summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp44
-rw-r--r--OWNERS2
-rw-r--r--PACKAGE_MANAGER_OWNERS2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java3
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java4
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java3
-rw-r--r--core/api/current.txt31
-rw-r--r--core/api/module-lib-current.txt5
-rw-r--r--core/api/system-current.txt9
-rw-r--r--core/api/test-current.txt2
-rw-r--r--core/java/android/app/ActivityThread.java2
-rw-r--r--core/java/android/app/AppOpsManager.java174
-rw-r--r--core/java/android/app/Notification.java6
-rw-r--r--core/java/android/app/OWNERS4
-rw-r--r--core/java/android/app/SystemServiceRegistry.java120
-rw-r--r--core/java/android/app/backup/BackupAgent.java20
-rw-r--r--core/java/android/app/multitasking.aconfig8
-rw-r--r--core/java/android/app/notification.aconfig7
-rw-r--r--core/java/android/companion/virtual/VirtualDevice.java41
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java25
-rw-r--r--core/java/android/content/AttributionSource.java5
-rw-r--r--core/java/android/content/pm/flags.aconfig8
-rw-r--r--core/java/android/content/pm/multiuser.aconfig7
-rw-r--r--core/java/android/content/pm/overlay/OverlayPaths.java7
-rw-r--r--core/java/android/content/pm/verify/domain/OWNERS5
-rw-r--r--core/java/android/content/res/ResourcesImpl.java21
-rw-r--r--core/java/android/credentials/flags.aconfig9
-rw-r--r--core/java/android/hardware/Camera.java2
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java5
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig7
-rw-r--r--core/java/android/net/flags.aconfig47
-rw-r--r--core/java/android/os/incremental/OWNERS6
-rw-r--r--core/java/android/os/vibrator/VibrationConfig.java41
-rw-r--r--core/java/android/provider/CallLog.java2
-rw-r--r--core/java/android/provider/Telephony.java2
-rw-r--r--core/java/android/security/OWNERS2
-rw-r--r--core/java/android/text/FontConfig.java64
-rw-r--r--core/java/android/text/flags/flags.aconfig7
-rw-r--r--core/java/android/util/Slog.java3
-rw-r--r--core/java/android/view/FocusFinder.java21
-rw-r--r--core/java/android/view/HdrRenderState.java39
-rw-r--r--core/java/android/view/KeyEvent.java26
-rw-r--r--core/java/android/view/View.java7
-rw-r--r--core/java/android/view/ViewRootImpl.java45
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java2
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl2
-rw-r--r--core/java/android/webkit/flags.aconfig9
-rw-r--r--core/java/android/widget/RemoteViews.java8
-rw-r--r--core/java/android/widget/TextView.java35
-rw-r--r--core/java/android/widget/flags/notification_widget_flags.aconfig8
-rw-r--r--core/java/com/android/internal/app/IAppOpsActiveCallback.aidl2
-rw-r--r--core/java/com/android/internal/app/IAppOpsCallback.aidl2
-rw-r--r--core/java/com/android/internal/app/IAppOpsNotedCallback.aidl3
-rw-r--r--core/java/com/android/internal/app/IAppOpsStartedCallback.aidl4
-rw-r--r--core/java/com/android/internal/os/AndroidPrintStream.java1
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java28
-rw-r--r--core/java/com/android/internal/policy/PhoneWindow.java8
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl11
-rw-r--r--core/jni/Android.bp1
-rw-r--r--core/jni/android_media_AudioSystem.cpp54
-rw-r--r--core/jni/android_tracing_PerfettoDataSource.cpp1
-rw-r--r--core/jni/android_tracing_PerfettoDataSource.h2
-rw-r--r--core/jni/android_tracing_PerfettoDataSourceInstance.cpp1
-rw-r--r--core/jni/android_tracing_PerfettoDataSourceInstance.h1
-rw-r--r--core/jni/android_tracing_PerfettoProducer.cpp1
-rw-r--r--core/jni/android_view_InputChannel.cpp10
-rw-r--r--core/jni/android_view_InputEventReceiver.cpp6
-rw-r--r--core/jni/android_view_InputEventSender.cpp6
-rw-r--r--core/proto/OWNERS2
-rw-r--r--core/res/AndroidManifest.xml10
-rw-r--r--core/res/res/drawable-nodpi/platlogo.xml220
-rw-r--r--core/res/res/drawable/ic_satellite_alt_24px.xml25
-rw-r--r--core/res/res/values/config.xml34
-rw-r--r--core/res/res/values/strings.xml15
-rw-r--r--core/res/res/values/symbols.xml11
-rw-r--r--core/tests/coretests/Android.bp1
-rw-r--r--core/tests/coretests/src/android/app/backup/BackupAgentTest.java54
-rw-r--r--core/tests/coretests/src/android/graphics/FontListParserTest.java139
-rw-r--r--core/tests/coretests/src/android/util/LogTest.java7
-rw-r--r--core/tests/coretests/src/android/widget/RemoteViewsTest.java9
-rw-r--r--core/tests/utiltests/Android.bp1
-rw-r--r--core/tests/utiltests/src/android/util/SlogTest.java7
-rw-r--r--data/etc/OWNERS7
-rw-r--r--data/fonts/font_fallback.xml225
-rw-r--r--data/fonts/font_fallback_cjkvf.xml381
-rw-r--r--graphics/java/android/graphics/Canvas.java20
-rw-r--r--graphics/java/android/graphics/FontListParser.java62
-rw-r--r--graphics/java/android/graphics/Matrix44.java472
-rw-r--r--graphics/java/android/graphics/fonts/SystemFonts.java72
-rw-r--r--libs/WindowManager/Shell/Android.bp8
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig21
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/CounterRotator.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java)2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java)7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt101
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt38
-rw-r--r--libs/hwui/SkiaCanvas.cpp4
-rw-r--r--libs/hwui/SkiaCanvas.h1
-rw-r--r--libs/hwui/hwui/Canvas.h1
-rw-r--r--libs/hwui/jni/android_graphics_Canvas.cpp8
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp92
-rw-r--r--location/java/android/location/altitude/AltitudeConverter.java21
-rw-r--r--media/java/android/media/AudioManager.java48
-rw-r--r--media/java/android/media/AudioSystem.java3
-rw-r--r--media/java/android/media/metrics/EditingEndedEvent.java24
-rw-r--r--media/java/android/media/tv/ITvInputClient.aidl2
-rw-r--r--media/java/android/media/tv/ITvInputManager.aidl2
-rw-r--r--media/java/android/media/tv/ITvInputSession.aidl2
-rw-r--r--media/java/android/media/tv/ITvInputSessionCallback.aidl2
-rw-r--r--media/java/android/media/tv/ITvInputSessionWrapper.java14
-rw-r--r--media/java/android/media/tv/SignalingDataInfo.aidl3
-rw-r--r--media/java/android/media/tv/SignalingDataInfo.java117
-rw-r--r--media/java/android/media/tv/SignalingDataResponse.aidl3
-rw-r--r--media/java/android/media/tv/SignalingDataResponse.java100
-rw-r--r--media/java/android/media/tv/TvInputManager.java253
-rw-r--r--media/java/android/media/tv/TvInputService.java50
-rw-r--r--media/java/android/media/tv/ad/ITvAdClient.aidl8
-rw-r--r--media/java/android/media/tv/ad/ITvAdManager.aidl4
-rw-r--r--media/java/android/media/tv/ad/ITvAdSession.aidl2
-rw-r--r--media/java/android/media/tv/ad/ITvAdSessionCallback.aidl7
-rw-r--r--media/java/android/media/tv/ad/ITvAdSessionWrapper.java13
-rw-r--r--media/java/android/media/tv/ad/TvAdManager.java531
-rw-r--r--media/java/android/media/tv/ad/TvAdService.java185
-rw-r--r--media/java/android/media/tv/ad/TvAdView.java287
-rw-r--r--media/java/android/media/tv/flags/media_tv.aconfig7
-rw-r--r--nfc/api/system-current.txt2
-rw-r--r--nfc/java/android/nfc/NfcAdapter.java8
-rw-r--r--packages/CredentialManager/res/drawable/more_horiz_24px.xml1
-rw-r--r--packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml3
-rw-r--r--packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml2
-rw-r--r--packages/CredentialManager/res/values/dimens.xml1
-rw-r--r--packages/CredentialManager/res/values/strings.xml2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt2
-rw-r--r--packages/CtsShim/OWNERS5
-rw-r--r--packages/PackageInstaller/AndroidManifest.xml8
-rw-r--r--packages/PackageInstaller/OWNERS5
-rw-r--r--packages/PackageInstaller/res/values/themes.xml5
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt1
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt8
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt31
-rw-r--r--packages/SettingsLib/res/values/strings.xml6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java20
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java3
-rw-r--r--packages/SystemUI/AndroidManifest.xml20
-rw-r--r--packages/SystemUI/aconfig/cross_device_control.aconfig15
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig14
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt30
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt77
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt139
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt40
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt132
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt18
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt11
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt10
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt53
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt28
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt36
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt8
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt14
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt319
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt44
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt58
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt6
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt38
-rw-r--r--packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt65
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt104
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt47
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt45
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt44
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt41
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt255
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt120
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt202
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt124
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt157
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt79
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt67
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt142
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt57
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt1
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java21
-rw-r--r--packages/SystemUI/res/drawable-nodpi/homecontrols_sq.pngbin0 -> 257239 bytes
-rw-r--r--packages/SystemUI/res/values/config.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/res/xml/home_controls_dream_metadata.xml19
-rw-r--r--packages/SystemUI/shared/Android.bp1
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt85
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt166
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt104
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputmethod/InputMethodModule.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt139
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt126
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt196
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt96
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt99
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt127
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt73
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt)4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DeviceStateRepositoryKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt55
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt27
-rw-r--r--ravenwood/README-ravenwood+mockito.md24
-rw-r--r--ravenwood/bulk_enable.py75
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java14
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java8
-rw-r--r--ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java6
-rwxr-xr-xravenwood/list-ravenwood-tests.sh18
-rw-r--r--ravenwood/mockito/Android.bp12
-rw-r--r--ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java10
-rw-r--r--ravenwood/ravenwood-annotation-allowed-classes.txt2
-rwxr-xr-xravenwood/run-ravenwood-tests.sh22
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java4
-rw-r--r--services/autofill/features.aconfig9
-rw-r--r--services/backup/Android.bp6
-rw-r--r--services/backup/flags.aconfig8
-rw-r--r--services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java1
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/com/android/server/IntentResolver.java6
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java5
-rw-r--r--services/core/java/com/android/server/am/AppPermissionTracker.java2
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java1
-rw-r--r--services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java2
-rw-r--r--services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java3
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java623
-rw-r--r--services/core/java/com/android/server/appop/AttributedOp.java97
-rw-r--r--services/core/java/com/android/server/appop/OnOpModeChangedListener.java17
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java12
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java22
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java16
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/EnrollClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/RemovalClient.java14
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java8
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java8
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java6
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java8
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java20
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java10
-rw-r--r--services/core/java/com/android/server/camera/CameraServiceProxy.java2
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java19
-rw-r--r--services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java8
-rw-r--r--services/core/java/com/android/server/hdmi/OneTouchPlayAction.java50
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java104
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodSettings.java7
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java18
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/om/IdmapManager.java2
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerService.java115
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerServiceImpl.java14
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerSettings.java18
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java8
-rw-r--r--services/core/java/com/android/server/pm/DexOptHelper.java77
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java89
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java3
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java11
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java4
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java145
-rw-r--r--services/core/java/com/android/server/pm/PackageSetting.java18
-rw-r--r--services/core/java/com/android/server/pm/ResolveIntentHelper.java4
-rw-r--r--services/core/java/com/android/server/pm/Settings.java14
-rw-r--r--services/core/java/com/android/server/pm/pkg/PackageState.java8
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/OWNERS5
-rw-r--r--services/core/java/com/android/server/policy/PermissionPolicyService.java8
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java12
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java14
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java32
-rw-r--r--services/core/java/com/android/server/trust/TrustAgentWrapper.java2
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java34
-rw-r--r--services/core/java/com/android/server/tv/TvInputManagerService.java40
-rw-r--r--services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java165
-rw-r--r--services/core/java/com/android/server/vcn/VcnContext.java4
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationScaler.java37
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSettings.java19
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStepConductor.java50
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorControlService.java243
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorControllerHolder.java2
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java57
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java71
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java53
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java9
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java11
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimation.java3
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java22
-rw-r--r--services/core/java/com/android/server/wm/Task.java2
-rw-r--r--services/core/java/com/android/server/wm/Transition.java20
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java9
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java15
-rw-r--r--services/incremental/OWNERS8
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/AppOpService.kt6
-rw-r--r--services/profcollect/OWNERS4
-rw-r--r--services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java4
-rw-r--r--services/tests/InputMethodSystemServerTests/Android.bp1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java28
-rw-r--r--services/tests/mockingservicestests/Android.bp1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java10
-rw-r--r--services/tests/powerstatstests/Android.bp1
-rw-r--r--services/tests/servicestests/Android.bp2
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java74
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java60
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java70
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java32
-rw-r--r--services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java20
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java54
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java2
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java83
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java19
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java51
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java70
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java196
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java16
-rw-r--r--services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java23
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java16
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java6
-rw-r--r--telephony/java/android/telephony/NetworkRegistrationInfo.java17
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java12
-rw-r--r--telephony/java/android/telephony/euicc/EuiccManager.java31
-rw-r--r--tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java2
-rw-r--r--tools/aapt2/Android.bp36
-rw-r--r--tools/aapt2/Android.mk20
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java18
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java17
-rw-r--r--tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java5
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java4
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java2
-rw-r--r--wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java31
-rw-r--r--wifi/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java1
-rw-r--r--wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java1
-rw-r--r--wifi/wifi.aconfig9
564 files changed, 13000 insertions, 2997 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 98b62b3e2046..0ee7ace5f661 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -36,7 +36,9 @@ aconfig_srcjars = [
":android.location.flags-aconfig-java{.generated_srcjars}",
":android.media.tv.flags-aconfig-java{.generated_srcjars}",
":android.multiuser.flags-aconfig-java{.generated_srcjars}",
+ ":android.net.platform.flags-aconfig-java{.generated_srcjars}",
":android.net.vcn.flags-aconfig-java{.generated_srcjars}",
+ ":android.net.wifi.flags-aconfig-java{.generated_srcjars}",
":android.nfc.flags-aconfig-java{.generated_srcjars}",
":android.os.flags-aconfig-java{.generated_srcjars}",
":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
@@ -60,13 +62,13 @@ aconfig_srcjars = [
":android.webkit.flags-aconfig-java{.generated_srcjars}",
":android.widget.flags-aconfig-java{.generated_srcjars}",
":audio-framework-aconfig",
+ ":backup_flags_lib{.generated_srcjars}",
":camera_platform_flags_core_java_lib{.generated_srcjars}",
":com.android.hardware.input-aconfig-java{.generated_srcjars}",
":com.android.input.flags-aconfig-java{.generated_srcjars}",
":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
":com.android.media.flags.editing-aconfig-java{.generated_srcjars}",
- ":com.android.net.flags-aconfig-java{.generated_srcjars}",
":com.android.net.thread.flags-aconfig-java{.generated_srcjars}",
":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
":com.android.text.flags-aconfig-java{.generated_srcjars}",
@@ -108,7 +110,9 @@ stubs_defaults {
"android.media.midi-aconfig",
"android.media.tv.flags-aconfig",
"android.multiuser.flags-aconfig",
+ "android.net.platform.flags-aconfig",
"android.net.vcn.flags-aconfig",
+ "android.net.wifi.flags-aconfig",
"android.nfc.flags-aconfig",
"android.os.flags-aconfig",
"android.os.vibrator.flags-aconfig",
@@ -136,7 +140,6 @@ stubs_defaults {
"com.android.hardware.input.input-aconfig",
"com.android.input.flags-aconfig",
"com.android.media.flags.bettertogether-aconfig",
- "com.android.net.flags-aconfig",
"com.android.net.thread.flags-aconfig",
"com.android.server.flags.services-aconfig",
"com.android.text.flags-aconfig",
@@ -775,9 +778,10 @@ java_aconfig_library {
// Networking
aconfig_declarations {
- name: "com.android.net.flags-aconfig",
- package: "com.android.net.flags",
+ name: "android.net.platform.flags-aconfig",
+ package: "android.net.platform.flags",
srcs: ["core/java/android/net/flags.aconfig"],
+ visibility: [":__subpackages__"],
}
// Thread network
@@ -788,9 +792,10 @@ aconfig_declarations {
}
java_aconfig_library {
- name: "com.android.net.flags-aconfig-java",
- aconfig_declarations: "com.android.net.flags-aconfig",
+ name: "android.net.platform.flags-aconfig-java",
+ aconfig_declarations: "android.net.platform.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ visibility: [":__subpackages__"],
}
java_aconfig_library {
@@ -1092,4 +1097,29 @@ java_aconfig_library {
name: "android.crashrecovery.flags-aconfig-java",
aconfig_declarations: "android.crashrecovery.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
-} \ No newline at end of file
+}
+
+// Backup
+java_aconfig_library {
+ name: "backup_flags_lib",
+ aconfig_declarations: "backup_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Wifi
+aconfig_declarations {
+ name: "android.net.wifi.flags-aconfig",
+ package: "android.net.wifi.flags",
+ srcs: ["wifi/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.net.wifi.flags-aconfig-java",
+ aconfig_declarations: "android.net.wifi.flags-aconfig",
+ min_sdk_version: "30",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.wifi",
+ ],
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/OWNERS b/OWNERS
index 733157f7ea62..935b76814304 100644
--- a/OWNERS
+++ b/OWNERS
@@ -39,3 +39,5 @@ per-file *ravenwood* = file:ravenwood/OWNERS
per-file *Ravenwood* = file:ravenwood/OWNERS
per-file PERFORMANCE_OWNERS = file:/PERFORMANCE_OWNERS
+
+per-file PACKAGE_MANAGER_OWNERS = file:/PACKAGE_MANAGER_OWNERS \ No newline at end of file
diff --git a/PACKAGE_MANAGER_OWNERS b/PACKAGE_MANAGER_OWNERS
index e4549b4de164..eb5842ba58ce 100644
--- a/PACKAGE_MANAGER_OWNERS
+++ b/PACKAGE_MANAGER_OWNERS
@@ -1,3 +1,3 @@
-chiuwinson@google.com
+alexbuy@google.com
patb@google.com
schfan@google.com \ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index e08200b055d8..33f6899239c6 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -743,7 +743,8 @@ public class AppStateTrackerImpl implements AppStateTracker {
private final class AppOpsWatcher extends IAppOpsCallback.Stub {
@Override
- public void opChanged(int op, int uid, String packageName) throws RemoteException {
+ public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) throws RemoteException {
boolean restricted = false;
try {
restricted = mAppOpsService.checkOperation(TARGET_OP,
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index abf80089b985..39de0af3c8d0 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -2026,8 +2026,8 @@ public class AlarmManagerService extends SystemService {
iAppOpsService.startWatchingMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, null,
new IAppOpsCallback.Stub() {
@Override
- public void opChanged(int op, int uid, String packageName)
- throws RemoteException {
+ public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) throws RemoteException {
final int userId = UserHandle.getUserId(uid);
if (op != AppOpsManager.OP_SCHEDULE_EXACT_ALARM
|| !isExactAlarmChangeEnabled(packageName, userId)) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 357e139617ef..6635484b20b9 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -227,7 +227,7 @@ public class InternalResourceService extends SystemService {
private final IAppOpsCallback mApbListener = new IAppOpsCallback.Stub() {
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName, String persistentDeviceId) {
boolean restricted = false;
try {
restricted = mAppOpsService.checkOperation(
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index e589c214801a..19bc7160e16a 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -671,7 +671,8 @@ public class AppStandbyController
/*packageName=*/ null,
new IAppOpsCallback.Stub() {
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) {
final int userId = UserHandle.getUserId(uid);
synchronized (mSystemExemptionAppOpMode) {
mSystemExemptionAppOpMode.delete(uid);
diff --git a/core/api/current.txt b/core/api/current.txt
index fa906513e307..aec284287020 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13,6 +13,7 @@ package android {
field public static final String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
field public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
field public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
+ field @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") public static final String ACCESS_HIDDEN_PROFILES = "android.permission.ACCESS_HIDDEN_PROFILES";
field public static final String ACCESS_LOCATION_EXTRA_COMMANDS = "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS";
field public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION";
field public static final String ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE";
@@ -5095,10 +5096,12 @@ package android.app {
public static interface AppOpsManager.OnOpActiveChangedListener {
method public void onOpActiveChanged(@NonNull String, int, @NonNull String, boolean);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, int, boolean, int, int);
}
public static interface AppOpsManager.OnOpChangedListener {
method public void onOpChanged(String, String);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onOpChanged(@NonNull String, @NonNull String, int, @NonNull String);
}
public abstract static class AppOpsManager.OnOpNotedCallback {
@@ -6353,6 +6356,7 @@ package android.app {
field public static final String CATEGORY_STOPWATCH = "stopwatch";
field public static final String CATEGORY_SYSTEM = "sys";
field public static final String CATEGORY_TRANSPORT = "transport";
+ field @FlaggedApi("android.app.category_voicemail") public static final String CATEGORY_VOICEMAIL = "voicemail";
field public static final String CATEGORY_WORKOUT = "workout";
field @ColorInt public static final int COLOR_DEFAULT = 0; // 0x0
field @NonNull public static final android.os.Parcelable.Creator<android.app.Notification> CREATOR;
@@ -15571,6 +15575,7 @@ package android.graphics {
method public boolean clipRect(float, float, float, float);
method public boolean clipRect(int, int, int, int);
method public void concat(@Nullable android.graphics.Matrix);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat44(@Nullable android.graphics.Matrix44);
method public void disableZ();
method public void drawARGB(int, int, int, int);
method public void drawArc(@NonNull android.graphics.RectF, float, float, boolean, @NonNull android.graphics.Paint);
@@ -16219,6 +16224,24 @@ package android.graphics {
enum_constant public static final android.graphics.Matrix.ScaleToFit START;
}
+ @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public class Matrix44 {
+ ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44();
+ ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44(@NonNull android.graphics.Matrix);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 concat(@NonNull android.graphics.Matrix44);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(int, int);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void getValues(@NonNull float[]);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean invert();
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean isIdentity();
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public float[] map(float, float, float, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void map(float, float, float, float, @NonNull float[]);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void reset();
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 rotate(float, float, float, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 scale(float, float, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(int, int, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void setValues(@NonNull float[]);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 translate(float, float, float);
+ }
+
public class Mesh {
ctor public Mesh(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull android.graphics.RectF);
ctor public Mesh(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull java.nio.ShortBuffer, @NonNull android.graphics.RectF);
@@ -21172,6 +21195,7 @@ package android.media {
method public int getStreamMinVolume(int);
method public int getStreamVolume(int);
method public float getStreamVolumeDb(int, int, int);
+ method @FlaggedApi("android.media.audio.supported_device_types_api") @NonNull public java.util.Set<java.lang.Integer> getSupportedDeviceTypes(int);
method @NonNull public java.util.List<android.media.AudioMixerAttributes> getSupportedMixerAttributes(@NonNull android.media.AudioDeviceInfo);
method @Deprecated public int getVibrateSetting(int);
method public int getVolumeGroupIdForAttributes(@NonNull android.media.AudioAttributes);
@@ -25790,6 +25814,7 @@ package android.media.metrics {
field public static final int FINAL_STATE_CANCELED = 2; // 0x2
field public static final int FINAL_STATE_ERROR = 3; // 0x3
field public static final int FINAL_STATE_SUCCEEDED = 1; // 0x1
+ field public static final int TIME_SINCE_CREATED_UNKNOWN = -1; // 0xffffffff
}
@FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public static final class EditingEndedEvent.Builder {
@@ -25797,7 +25822,7 @@ package android.media.metrics {
method @NonNull public android.media.metrics.EditingEndedEvent build();
method @NonNull public android.media.metrics.EditingEndedEvent.Builder setErrorCode(int);
method @NonNull public android.media.metrics.EditingEndedEvent.Builder setMetricsBundle(@NonNull android.os.Bundle);
- method @NonNull public android.media.metrics.EditingEndedEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=android.media.metrics.EditingEndedEvent.TIME_SINCE_CREATED_UNKNOWN) long);
}
public final class EditingSession implements java.lang.AutoCloseable {
@@ -44520,6 +44545,7 @@ package android.telephony {
method @Nullable public android.telephony.CellIdentity getCellIdentity();
method public int getDomain();
method @Nullable public String getRegisteredPlmn();
+ method @FlaggedApi("com.android.internal.telephony.flags.network_registration_info_reject_cause") public int getRejectCause();
method public int getTransportType();
method public boolean isNetworkRegistered();
method public boolean isNetworkRoaming();
@@ -50649,6 +50675,7 @@ package android.view {
field public static final int KEYCODE_DVR = 173; // 0xad
field public static final int KEYCODE_E = 33; // 0x21
field public static final int KEYCODE_EISU = 212; // 0xd4
+ field @FlaggedApi("com.android.hardware.input.emoji_and_screenshot_keycodes_available") public static final int KEYCODE_EMOJI_PICKER = 317; // 0x13d
field public static final int KEYCODE_ENDCALL = 6; // 0x6
field public static final int KEYCODE_ENTER = 66; // 0x42
field public static final int KEYCODE_ENVELOPE = 65; // 0x41
@@ -50781,6 +50808,7 @@ package android.view {
field public static final int KEYCODE_RIGHT_BRACKET = 72; // 0x48
field public static final int KEYCODE_RO = 217; // 0xd9
field public static final int KEYCODE_S = 47; // 0x2f
+ field @FlaggedApi("com.android.hardware.input.emoji_and_screenshot_keycodes_available") public static final int KEYCODE_SCREENSHOT = 318; // 0x13e
field public static final int KEYCODE_SCROLL_LOCK = 116; // 0x74
field public static final int KEYCODE_SEARCH = 84; // 0x54
field public static final int KEYCODE_SEMICOLON = 74; // 0x4a
@@ -59542,7 +59570,6 @@ package android.widget {
}
@FlaggedApi("android.appwidget.flags.draw_data_parcel") public static final class RemoteViews.DrawInstructions {
- method @FlaggedApi("android.appwidget.flags.draw_data_parcel") public void appendInstructions(@NonNull byte[]);
}
@FlaggedApi("android.appwidget.flags.draw_data_parcel") public static final class RemoteViews.DrawInstructions.Builder {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 24b923326baa..55ed1f559f51 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -66,6 +66,11 @@ package android.app {
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
}
+ public final class SystemServiceRegistry {
+ method @FlaggedApi("android.webkit.update_service_ipc_wrapper") @Nullable public static Object getSystemServiceWithNoContext(@NonNull String);
+ method @FlaggedApi("android.webkit.update_service_ipc_wrapper") public static <TServiceClass> void registerForeverStaticService(@NonNull String, @NonNull Class<TServiceClass>, @NonNull android.app.SystemServiceRegistry.StaticServiceProducerWithBinder<TServiceClass>);
+ }
+
}
package android.app.admin {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f1011616c3ab..6afc9483af53 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -822,6 +822,7 @@ package android.app {
public static interface AppOpsManager.OnOpNotedListener {
method public void onOpNoted(@NonNull String, int, @NonNull String, @Nullable String, int, int);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onOpNoted(@NonNull String, int, @NonNull String, @Nullable String, int, int, int);
}
public static final class AppOpsManager.OpEntry implements android.os.Parcelable {
@@ -3208,8 +3209,14 @@ package android.companion {
package android.companion.virtual {
+ public final class VirtualDevice implements android.os.Parcelable {
+ method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomAudioInputSupport();
+ method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomCameraSupport();
+ }
+
public final class VirtualDeviceManager {
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.VirtualDeviceManager.VirtualDevice createVirtualDevice(int, @NonNull android.companion.virtual.VirtualDeviceParams);
+ method @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") @NonNull public java.util.Set<java.lang.String> getAllPersistentDeviceIds();
method @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") @Nullable public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String);
field public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; // 0x2
field public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; // 0x1
@@ -9657,6 +9664,7 @@ package android.net.wifi.nl80211 {
public final class DeviceWiphyCapabilities implements android.os.Parcelable {
ctor public DeviceWiphyCapabilities();
method public int describeContents();
+ method @FlaggedApi("android.net.wifi.flags.get_device_cross_akm_roaming_support") public int getMaxNumberAkms();
method public int getMaxNumberRxSpatialStreams();
method public int getMaxNumberTxSpatialStreams();
method public boolean isChannelWidthSupported(int);
@@ -14120,7 +14128,6 @@ package android.telephony {
method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
method public int getNetworkRegistrationState();
method @Deprecated public int getRegistrationState();
- method public int getRejectCause();
method public int getRoamingType();
method public boolean isEmergencyEnabled();
method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c546d7a4cfe8..77add41f6805 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3616,7 +3616,7 @@ package android.view {
method public final int getDisplayId();
method public final void setDisplayId(int);
field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
- field public static final int LAST_KEYCODE = 316; // 0x13c
+ field public static final int LAST_KEYCODE = 318; // 0x13e
}
public final class KeyboardShortcutGroup implements android.os.Parcelable {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5d2a26e13ee7..4c54b03bd6d6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -303,7 +303,7 @@ public final class ActivityThread extends ClientTransactionHandler
public static final boolean DEBUG_MEMORY_TRIM = false;
private static final boolean DEBUG_PROVIDER = false;
public static final boolean DEBUG_ORDER = false;
- private static final boolean DEBUG_APP_INFO = true;
+ private static final boolean DEBUG_APP_INFO = false;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
/**
* The delay to release the provider when it has no more references. It reduces the number of
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index bb666e65d6fb..0dbce97c978b 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -36,6 +36,7 @@ import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.usage.UsageStatsManager;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -1549,7 +1550,7 @@ public class AppOpsManager {
AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
/**
- * Allows an app whose primary use case is to backup or sync content to run longer jobs.
+ * Allows an app with a major use case of backing-up or syncing content to run longer jobs.
*
* @hide
*/
@@ -7346,6 +7347,12 @@ public class AppOpsManager {
* The default impl is to fallback onto {@link #onOpChanged(String, String)
*
+ * <p> Implement this method and not
+ * {@link #onOpChanged(String, String, int, String)} if
+ * callbacks are
+ * required only on op state changes for the default device
+ * {@link VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT}.
+ *
* @param op The Op that changed.
* @param packageName Package of the app whose Op changed.
* @param userId User Space of the app whose Op changed.
@@ -7354,6 +7361,31 @@ public class AppOpsManager {
default void onOpChanged(@NonNull String op, @NonNull String packageName, int userId) {
onOpChanged(op, packageName);
}
+
+ /**
+ * Similar to {@link #onOpChanged(String, String, int)} but includes the device for which
+ * the op mode has changed.
+ *
+ * <p> Implement this method if callbacks are required on all devices.
+ * If not implemented explicitly, the default implementation will notify for op changes
+ * on the default device {@link VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT} only.
+ *
+ * <p> If implemented, {@link #onOpChanged(String, String, int)}
+ * will not be called automatically.
+ *
+ * @param op The Op that changed.
+ * @param packageName Package of the app whose Op changed.
+ * @param userId User id of the app whose Op changed.
+ * @param persistentDeviceId persistent device id whose Op changed.
+ */
+ @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ default void onOpChanged(@NonNull String op, @NonNull String packageName, int userId,
+ @NonNull String persistentDeviceId) {
+ if (Objects.equals(persistentDeviceId,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {
+ onOpChanged(op, packageName, userId);
+ }
+ }
}
/**
@@ -7373,6 +7405,12 @@ public class AppOpsManager {
/**
* Called when the active state of an app-op changes.
*
+ * <p> Implement this method and not
+ * {@link #onOpActiveChanged(String, int, String, String, int, boolean, int, int)} if
+ * callbacks are
+ * required only on op state changes for the default device
+ * {@link Context#DEVICE_ID_DEFAULT}.
+ *
* @param op The operation that changed.
* @param uid The UID performing the operation.
* @param packageName The package performing the operation.
@@ -7388,6 +7426,37 @@ public class AppOpsManager {
int attributionFlags, int attributionChainId) {
onOpActiveChanged(op, uid, packageName, active);
}
+
+ /**
+ * Similar to {@link #onOpActiveChanged(String, int, String, String, boolean, int, int)},
+ * but also includes the virtual device id of the op is now active or inactive.
+ *
+ * <p> Implement this method if callbacks are required on all devices.
+ * If not implemented explicitly, the default implementation will notify for op state
+ * changes on the default device {@link Context#DEVICE_ID_DEFAULT} only.
+ *
+ * <p> If implemented,
+ * {@link #onOpActiveChanged(String, int, String, String, boolean, int, int)}
+ * will not be called automatically.
+ *
+ * @param op The operation that changed.
+ * @param uid The UID performing the operation.
+ * @param packageName The package performing the operation.
+ * @param attributionTag The operation's attribution tag.
+ * @param virtualDeviceId the virtual device id whose operation has changed
+ * @param active Whether the operation became active or inactive.
+ * @param attributionFlags the attribution flags for this operation.
+ * @param attributionChainId the unique id of the attribution chain this op is a part of.
+ */
+ @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ default void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, boolean active,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ onOpActiveChanged(op, uid, packageName, attributionTag, active, attributionFlags,
+ attributionChainId);
+ }
+ }
}
/**
@@ -7400,6 +7469,10 @@ public class AppOpsManager {
/**
* Called when an app-op is noted.
*
+ * <p> Implement this method and not
+ * {@link #onOpNoted(String, int, String, String, int, int, int)} if callbacks are
+ * required only on op notes for the default device {@link Context#DEVICE_ID_DEFAULT}.
+ *
* @param op The operation that was noted.
* @param uid The UID performing the operation.
* @param packageName The package performing the operation.
@@ -7409,6 +7482,34 @@ public class AppOpsManager {
*/
void onOpNoted(@NonNull String op, int uid, @NonNull String packageName,
@Nullable String attributionTag, @OpFlags int flags, @Mode int result);
+
+ /**
+ * Similar to {@link #onOpNoted(String, int, String, String, int, int, int)},
+ * but also includes the virtual device id of the op is now active or inactive.
+ *
+ * <p> Implement this method if callbacks are required for op notes on all devices.
+ * If not implemented explicitly, the default implementation will notify for the
+ * default device {@link Context#DEVICE_ID_DEFAULT} only.
+ *
+ * <p> If implemented, {@link #onOpNoted(String, int, String, String, int, int)}
+ * will not be called automatically.
+ *
+ * @param op The operation that was noted.
+ * @param uid The UID performing the operation.
+ * @param packageName The package performing the operation.
+ * @param attributionTag The attribution tag performing the operation.
+ * @param virtualDeviceId the device that noted the operation
+ * @param flags The flags of this op
+ * @param result The result of the note.
+ */
+ @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ default void onOpNoted(@NonNull String op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, @OpFlags int flags,
+ @Mode int result) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ onOpNoted(op, uid, packageName, attributionTag, flags, result);
+ }
+ }
}
/**
@@ -7447,6 +7548,9 @@ public class AppOpsManager {
public static class OnOpChangedInternalListener implements OnOpChangedListener {
public void onOpChanged(String op, String packageName) { }
public void onOpChanged(int op, String packageName) { }
+ public void onOpChanged(int op, String packageName, String persistentDeviceId) {
+ onOpChanged(op, packageName);
+ }
}
/**
@@ -7457,6 +7561,8 @@ public class AppOpsManager {
public interface OnOpActiveChangedInternalListener extends OnOpActiveChangedListener {
default void onOpActiveChanged(String op, int uid, String packageName, boolean active) { }
default void onOpActiveChanged(int op, int uid, String packageName, boolean active) { }
+ default void onOpActiveChanged(int op, int uid, String packageName, int virtualDeviceId,
+ boolean active) { }
}
/**
@@ -7510,6 +7616,12 @@ public class AppOpsManager {
/**
* Called when an op was started.
*
+ * <p> Implement this method and not
+ * {@link #onOpStarted(int, int, String, String, int, int, int, int, int, int)} if
+ * callbacks are
+ * required only on op starts for the default device
+ * {@link Context#DEVICE_ID_DEFAULT}.
+ *
* Note: This is only for op starts. It is not called when an op is noted or stopped.
* By default, unless this method is overridden, no code will be executed for resume
* events.
@@ -7530,6 +7642,37 @@ public class AppOpsManager {
onOpStarted(op, uid, packageName, attributionTag, flags, result);
}
}
+
+ /**
+ * Similar to {@link #onOpStarted(int, int, String, String, int, int)},
+ * but also includes the virtual device id that started the op.
+ *
+ * <p> Implement this method if callbacks are required on all devices.
+ * If not implemented explicitly, the default implementation will notify for op starts on
+ * the default device {@link Context#DEVICE_ID_DEFAULT} only.
+ *
+ * <p> If implemented, {@link #onOpStarted(int, int, String, String, int, int)}
+ * will not be called automatically.
+ *
+ * @param op The op code.
+ * @param uid The UID performing the operation.
+ * @param packageName The package performing the operation.
+ * @param attributionTag The attribution tag performing the operation.
+ * @param virtualDeviceId the device that started the operation
+ * @param flags The flags of this op.
+ * @param result The result of the start.
+ * @param startType The start type of this start event. Either failed, resumed, or started.
+ * @param attributionFlags The location of this started op in an attribution chain.
+ */
+ default void onOpStarted(int op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, @OpFlags int flags,
+ @Mode int result, @StartedType int startType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ onOpStarted(op, uid, packageName, attributionTag, flags, result, startType,
+ attributionFlags, attributionChainId);
+ }
+ }
}
AppOpsManager(Context context, IAppOpsService service) {
@@ -8054,14 +8197,16 @@ public class AppOpsManager {
IAppOpsCallback cb = mModeWatchers.get(callback);
if (cb == null) {
cb = new IAppOpsCallback.Stub() {
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) {
if (callback instanceof OnOpChangedInternalListener) {
- ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName);
+ ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName,
+ persistentDeviceId);
}
if (sAppOpInfos[op].name != null) {
callback.onOpChanged(sAppOpInfos[op].name, packageName,
- UserHandle.getUserId(uid));
+ UserHandle.getUserId(uid), persistentDeviceId);
}
}
};
@@ -8142,16 +8287,17 @@ public class AppOpsManager {
cb = new IAppOpsActiveCallback.Stub() {
@Override
public void opActiveChanged(int op, int uid, String packageName,
- String attributionTag, boolean active, @AttributionFlags
- int attributionFlags, int attributionChainId) {
+ String attributionTag, int virtualDeviceId, boolean active,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
executor.execute(() -> {
if (callback instanceof OnOpActiveChangedInternalListener) {
((OnOpActiveChangedInternalListener) callback).onOpActiveChanged(op,
- uid, packageName, active);
+ uid, packageName, virtualDeviceId, active);
}
if (sAppOpInfos[op].name != null) {
callback.onOpActiveChanged(sAppOpInfos[op].name, uid, packageName,
- attributionTag, active, attributionFlags, attributionChainId);
+ attributionTag, virtualDeviceId, active, attributionFlags,
+ attributionChainId);
}
});
}
@@ -8220,10 +8366,10 @@ public class AppOpsManager {
cb = new IAppOpsStartedCallback.Stub() {
@Override
public void opStarted(int op, int uid, String packageName, String attributionTag,
- int flags, int mode, int startType, int attributionFlags,
- int attributionChainId) {
- callback.onOpStarted(op, uid, packageName, attributionTag, flags, mode,
- startType, attributionFlags, attributionChainId);
+ int virtualDeviceId, int flags, int mode, int startType,
+ int attributionFlags, int attributionChainId) {
+ callback.onOpStarted(op, uid, packageName, attributionTag, virtualDeviceId,
+ flags, mode, startType, attributionFlags, attributionChainId);
}
};
mStartedWatchers.put(callback, cb);
@@ -8391,13 +8537,13 @@ public class AppOpsManager {
cb = new IAppOpsNotedCallback.Stub() {
@Override
public void opNoted(int op, int uid, String packageName, String attributionTag,
- int flags, int mode) {
+ int virtualDeviceId, int flags, int mode) {
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(() -> {
if (sAppOpInfos[op].name != null) {
listener.onOpNoted(sAppOpInfos[op].name, uid, packageName,
- attributionTag,
+ attributionTag, virtualDeviceId,
flags, mode);
}
});
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a81ad3c429ea..d705eeb706e8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1061,6 +1061,12 @@ public class Notification implements Parcelable
public static final String CATEGORY_MISSED_CALL = "missed_call";
/**
+ * Notification category: voicemail.
+ */
+ @FlaggedApi(Flags.FLAG_CATEGORY_VOICEMAIL)
+ public static final String CATEGORY_VOICEMAIL = "voicemail";
+
+ /**
* One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
* that best describes this Notification. May be used by the system for ranking and filtering.
*/
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 3b5bba20a10b..729f92a73e5b 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -111,5 +111,9 @@ per-file Window* = file:/services/core/java/com/android/server/wm/OWNERS
per-file ConfigurationController.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file *ScreenCapture* = file:/services/core/java/com/android/server/wm/OWNERS
+# Multitasking
+per-file multitasking.aconfig = file:/services/core/java/com/android/server/wm/OWNERS
+per-file multitasking.aconfig = file:/libs/WindowManager/Shell/OWNERS
+
# Zygote
per-file *Zygote* = file:/ZYGOTE_OWNERS
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 397b63fe6a30..ba9c8952892b 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -19,6 +19,7 @@ package android.app;
import android.accounts.AccountManager;
import android.accounts.IAccountManager;
import android.adservices.AdServicesFrameworkInitializer;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -1617,6 +1618,19 @@ public final class SystemServiceRegistry {
return new ContactKeysManager(ctx);
}});
+ // DO NOT do a flag check like this unless the flag is read-only.
+ // (because this code is executed during preload in zygote.)
+ // If the flag is mutable, the check should be inside CachedServiceFetcher.
+ if (Flags.bicClient()) {
+ registerService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE,
+ BackgroundInstallControlManager.class,
+ new CachedServiceFetcher<BackgroundInstallControlManager>() {
+ @Override
+ public BackgroundInstallControlManager createService(ContextImpl ctx) {
+ return new BackgroundInstallControlManager(ctx);
+ }
+ });
+ }
sInitializing = true;
try {
// Note: the following functions need to be @SystemApis, once they become mainline
@@ -1668,11 +1682,7 @@ public final class SystemServiceRegistry {
return new Object[sServiceCacheSize];
}
- /**
- * Gets a system service from a given context.
- * @hide
- */
- public static Object getSystemService(ContextImpl ctx, String name) {
+ private static ServiceFetcher<?> getSystemServiceFetcher(String name) {
if (name == null) {
return null;
}
@@ -1683,6 +1693,18 @@ public final class SystemServiceRegistry {
}
return null;
}
+ return fetcher;
+ }
+
+ /**
+ * Gets a system service from a given context.
+ * @hide
+ */
+ public static Object getSystemService(@NonNull ContextImpl ctx, String name) {
+ final ServiceFetcher<?> fetcher = getSystemServiceFetcher(name);
+ if (fetcher == null) {
+ return null;
+ }
final Object ret = fetcher.getService(ctx);
if (sEnableServiceNotFoundWtf && ret == null) {
@@ -1710,6 +1732,26 @@ public final class SystemServiceRegistry {
}
/**
+ * Gets a system service which has opted-in to being fetched without a context.
+ * @hide
+ */
+ @FlaggedApi(android.webkit.Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static @Nullable Object getSystemServiceWithNoContext(@NonNull String name) {
+ final ServiceFetcher<?> fetcher = getSystemServiceFetcher(name);
+ if (fetcher == null) {
+ return null;
+ }
+
+ if (!fetcher.supportsFetchWithoutContext()) {
+ throw new IllegalArgumentException(
+ "Manager cannot be fetched without a context: " + name);
+ }
+
+ return fetcher.getService(null);
+ }
+
+ /**
* Gets the name of the system-level service that is represented by the specified class.
* @hide
*/
@@ -1863,6 +1905,50 @@ public final class SystemServiceRegistry {
}
/**
+ * Used by apex modules to register a "service wrapper" that is not tied to any {@link Context}
+ * and will never require a context in the future.
+ *
+ * Services registered in this way can be fetched via
+ * {@link #getSystemServiceWithNoContext(String)}, so cannot require a context in future without
+ * a breaking change.
+ *
+ * <p>This can only be called from the methods called by the static initializer of
+ * {@link SystemServiceRegistry}. (Otherwise it throws a {@link IllegalStateException}.)
+ *
+ * @param serviceName the name of the binder object, such as
+ * {@link Context#JOB_SCHEDULER_SERVICE}.
+ * @param serviceWrapperClass the wrapper class, such as the class of
+ * {@link android.app.job.JobScheduler}.
+ * @param serviceProducer Callback that takes the service binder object with the name
+ * {@code serviceName} and returns an actual service wrapper instance.
+ *
+ * @hide
+ */
+ @FlaggedApi(android.webkit.Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static <TServiceClass> void registerForeverStaticService(
+ @NonNull String serviceName, @NonNull Class<TServiceClass> serviceWrapperClass,
+ @NonNull StaticServiceProducerWithBinder<TServiceClass> serviceProducer) {
+ ensureInitializing("registerStaticService");
+ Preconditions.checkStringNotEmpty(serviceName);
+ Objects.requireNonNull(serviceWrapperClass);
+ Objects.requireNonNull(serviceProducer);
+
+ registerService(serviceName, serviceWrapperClass,
+ new StaticServiceFetcher<TServiceClass>() {
+ @Override
+ public TServiceClass createService() throws ServiceNotFoundException {
+ return serviceProducer.createService(
+ ServiceManager.getServiceOrThrow(serviceName));
+ }
+
+ @Override
+ public boolean supportsFetchWithoutContext() {
+ return true;
+ }});
+ }
+
+ /**
* Similar to {@link #registerStaticService(String, Class, StaticServiceProducerWithBinder)},
* but used for a "service wrapper" that doesn't take a service binder in its constructor.
*
@@ -1952,6 +2038,18 @@ public final class SystemServiceRegistry {
*/
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
+
+ /**
+ * Should this service fetcher support being fetched via {@link #getSystemService(String)},
+ * without a Context?
+ *
+ * This means that the service cannot depend on a Context in future!
+ *
+ * @return true if this is supported for this service.
+ */
+ default boolean supportsFetchWithoutContext() {
+ return false;
+ }
}
/**
@@ -2059,6 +2157,11 @@ public final class SystemServiceRegistry {
}
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
+
+ // Services that explicitly use a Context can never be fetched without one.
+ public final boolean supportsFetchWithoutContext() {
+ return false;
+ }
}
/**
@@ -2083,6 +2186,13 @@ public final class SystemServiceRegistry {
}
public abstract T createService() throws ServiceNotFoundException;
+
+ // Services that do not need a Context can potentially be fetched without one, but the
+ // default is false, so that the service can require one in future without this being a
+ // breaking change.
+ public boolean supportsFetchWithoutContext() {
+ return false;
+ }
}
/** @hide */
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 6b558d07c059..ffbd80ce5824 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -43,12 +43,14 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
+import com.android.server.backup.Flags;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -1283,6 +1285,14 @@ public abstract class BackupAgent extends ContextWrapper {
// And bring live SharedPreferences instances up to date
reloadSharedPreferences();
+ // It's possible that onRestoreFile was overridden and that the agent did not
+ // consume all the data for this file from the pipe. We need to clear the pipe,
+ // otherwise the framework can get stuck trying to write to a full pipe or
+ // onRestoreFile could be called with the previous file's data left in the pipe.
+ if (Flags.enableClearPipeAfterRestoreFile()) {
+ clearUnconsumedDataFromPipe(data, size);
+ }
+
Binder.restoreCallingIdentity(ident);
try {
callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
@@ -1296,6 +1306,16 @@ public abstract class BackupAgent extends ContextWrapper {
}
}
+ private static void clearUnconsumedDataFromPipe(ParcelFileDescriptor data, long size) {
+ try (FileInputStream in = new FileInputStream(data.getFileDescriptor())) {
+ if (in.available() > 0) {
+ in.skip(size);
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to clear unconsumed data from pipe.", e);
+ }
+ }
+
@Override
public void doRestoreFinished(int token, IBackupManager callbackBinder) {
final long ident = Binder.clearCallingIdentity();
diff --git a/core/java/android/app/multitasking.aconfig b/core/java/android/app/multitasking.aconfig
new file mode 100644
index 000000000000..ab00891b9b31
--- /dev/null
+++ b/core/java/android/app/multitasking.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+ name: "enable_pip_ui_state_callback_on_entering"
+ namespace: "multitasking"
+ description: "Enables PiP UI state callback on entering"
+ bug: "303718131"
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index d11c6c50da3b..a5d4a1479afb 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -43,3 +43,10 @@ flag {
description: "Fixes the behavior of KeyguardManager#setPrivateNotificationsAllowed()"
bug: "309920145"
}
+
+flag {
+ name: "category_voicemail"
+ namespace: "wear_sysui"
+ description: "Adds a new voicemail category for notifications"
+ bug: "322806700"
+}
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index d0c8be6ca896..97fa2ba2638d 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -17,11 +17,14 @@
package android.companion.virtual;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.companion.virtual.flags.Flags;
import android.content.Context;
import android.os.Parcel;
@@ -164,6 +167,44 @@ public final class VirtualDevice implements Parcelable {
}
}
+ /**
+ * Returns whether this device may have custom audio input device.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
+ public boolean hasCustomAudioInputSupport() {
+ try {
+ return mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO) == DEVICE_POLICY_CUSTOM;
+ // TODO(b/291735254): also check for a custom audio injection mix for this device id.
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether this device may have custom cameras.
+ *
+ * <p>Returning {@code true} does not necessarily mean that this device has cameras, it only
+ * means that a {@link android.hardware.camera2.CameraManager} instance created from a
+ * {@link Context} associated with this device will return this device's cameras, if any.</p>
+ *
+ * @see Context#getDeviceId()
+ * @see Context#createDeviceContext(int)
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
+ public boolean hasCustomCameraSupport() {
+ try {
+ return mVirtualDevice.getDevicePolicy(POLICY_TYPE_CAMERA) == DEVICE_POLICY_CUSTOM;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 90d251b04f67..a16e94a534a9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -63,6 +63,7 @@ import android.media.AudioManager;
import android.os.Binder;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;
@@ -74,9 +75,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;
@@ -389,6 +392,28 @@ public final class VirtualDeviceManager {
}
/**
+ * Returns all current persistent device IDs, including the ones for which no virtual device
+ * exists, as long as one may have existed or can be created.
+ *
+ * @hide
+ */
+ // TODO(b/315481938): Link @see VirtualDevice#getPersistentDeviceId()
+ @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API)
+ @SystemApi
+ @NonNull
+ public Set<String> getAllPersistentDeviceIds() {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve persistent ids; no virtual device manager service.");
+ return Collections.emptySet();
+ }
+ try {
+ return new ArraySet<>(mService.getAllPersistentDeviceIds());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
* {@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
* device which is not a virtual device. {@code deviceId} must correspond to a virtual device
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index bde562dbf95b..af1301140358 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -409,9 +409,10 @@ public final class AttributionSource implements Parcelable {
"packageName = " + mAttributionSourceState.packageName + ", " +
"attributionTag = " + mAttributionSourceState.attributionTag + ", " +
"token = " + mAttributionSourceState.token + ", " +
+ "deviceId = " + mAttributionSourceState.deviceId + ", " +
"next = " + (mAttributionSourceState.next != null
- && mAttributionSourceState.next.length > 0
- ? mAttributionSourceState.next[0] : null) +
+ && mAttributionSourceState.next.length > 0
+ ? new AttributionSource(mAttributionSourceState.next[0]).toString() : null) +
" }";
}
return super.toString();
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index caff4576c9c3..629658b971a5 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -183,3 +183,11 @@ flag {
description: "Feature flag to enable permission EMERGENCY_INSTALL_PACKAGES"
bug: "321080601"
}
+
+flag {
+ name: "asl_in_apk_app_metadata_source"
+ namespace: "package_manager_service"
+ description: "Feature flag to allow to know if the Android Safety Label (ASL) of an app is provided by the app's APK itself, or provided by an installer."
+ bug: "287487923"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index efb8607f75f7..25fd3e0084b5 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -108,3 +108,10 @@ flag {
bug: "316362775"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_permission_to_access_hidden_profiles"
+ namespace: "profile_experiences"
+ description: "Add permission to access API hidden users data via system APIs"
+ bug: "321988638"
+}
diff --git a/core/java/android/content/pm/overlay/OverlayPaths.java b/core/java/android/content/pm/overlay/OverlayPaths.java
index bd74b0b9293c..a4db733af013 100644
--- a/core/java/android/content/pm/overlay/OverlayPaths.java
+++ b/core/java/android/content/pm/overlay/OverlayPaths.java
@@ -49,13 +49,6 @@ public class OverlayPaths {
public static class Builder {
final OverlayPaths mPaths = new OverlayPaths();
- public Builder() {}
-
- public Builder(@NonNull OverlayPaths base) {
- mPaths.mResourceDirs.addAll(base.getResourceDirs());
- mPaths.mOverlayPaths.addAll(base.getOverlayPaths());
- }
-
/**
* Adds a non-APK path to the contents of {@link OverlayPaths#getOverlayPaths()}.
*/
diff --git a/core/java/android/content/pm/verify/domain/OWNERS b/core/java/android/content/pm/verify/domain/OWNERS
index c669112e0512..b451fe4b1077 100644
--- a/core/java/android/content/pm/verify/domain/OWNERS
+++ b/core/java/android/content/pm/verify/domain/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 36137
+include /PACKAGE_MANAGER_OWNERS
-chiuwinson@google.com
-patb@google.com
-toddke@google.com \ No newline at end of file
+wloh@google.com \ No newline at end of file
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index c7790bd96c62..5e442b819774 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -273,14 +273,27 @@ public class ResourcesImpl {
throw new NotFoundException("String resource name " + name);
}
+ private static boolean isIntLike(@NonNull String s) {
+ if (s.isEmpty() || s.length() > 10) return false;
+ for (int i = 0, size = s.length(); i < size; i++) {
+ final char c = s.charAt(i);
+ if (c < '0' || c > '9') {
+ return false;
+ }
+ }
+ return true;
+ }
+
int getIdentifier(String name, String defType, String defPackage) {
if (name == null) {
throw new NullPointerException("name is null");
}
- try {
- return Integer.parseInt(name);
- } catch (Exception e) {
- // Ignore
+ if (isIntLike(name)) {
+ try {
+ return Integer.parseInt(name);
+ } catch (Exception e) {
+ // Ignore
+ }
}
return mAssets.getResourceIdentifier(name, defType, defPackage);
}
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 90cd4718fcee..1ca11e698d5d 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -47,4 +47,11 @@ flag {
name: "configurable_selector_ui_enabled"
description: "Enables OEM configurable Credential Selector UI"
bug: "319448437"
-} \ No newline at end of file
+}
+
+flag {
+ namespace: "credential_manager"
+ name: "credman_biometric_api_enabled"
+ description: "Enables Credential Manager to work with the Biometric Authenticate API"
+ bug: "323211850"
+}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 039644387715..1c36a88726b8 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -1575,7 +1575,7 @@ public class Camera {
}
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName, String persistentDeviceId) {
if (op == AppOpsManager.OP_PLAY_AUDIO) {
final Camera camera = mWeakCamera.get();
if (camera != null) {
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 64a62a9299b8..f18a0b758bc5 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -202,8 +202,11 @@ public abstract class DisplayManagerInternal {
/**
* Called by the window manager to perform traversals while holding a
* surface flinger transaction.
+ * @param t The default transaction.
+ * @param displayTransactions The transactions mapped by display id.
*/
- public abstract void performTraversal(Transaction t);
+ public abstract void performTraversal(Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions);
/**
* Tells the display manager about properties of the display that depend on the windows on it.
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 17992aad68e8..e070fe570907 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -33,6 +33,13 @@ flag {
flag {
namespace: "input_native"
+ name: "emoji_and_screenshot_keycodes_available"
+ description: "Add new KeyEvent keycodes for opening Emoji Picker and Taking Screenshots"
+ bug: "315307777"
+}
+
+flag {
+ namespace: "input_native"
name: "keyboard_a11y_slow_keys_flag"
description: "Controls if the slow keys accessibility feature for physical keyboard is available to the user"
bug: "294546335"
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 0ad1804f402c..311dc09eb516 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -1,50 +1,11 @@
-package: "com.android.net.flags"
+package: "android.net.platform.flags"
-flag {
- name: "track_multiple_network_activities"
- namespace: "android_core_networking"
- description: "NetworkActivityTracker tracks multiple networks including non default networks"
- bug: "267870186"
-}
-
-flag {
- name: "forbidden_capability"
- namespace: "android_core_networking"
- description: "This flag controls the forbidden capability API"
- bug: "302997505"
-}
-
-flag {
- name: "set_data_saver_via_cm"
- namespace: "android_core_networking"
- description: "Set data saver through ConnectivityManager API"
- bug: "297836825"
-}
-
-flag {
- name: "support_is_uid_networking_blocked"
- namespace: "android_core_networking"
- description: "This flag controls whether isUidNetworkingBlocked is supported"
- bug: "297836825"
-}
-
-flag {
- name: "basic_background_restrictions_enabled"
- namespace: "android_core_networking"
- description: "Block network access for apps in a low importance background state"
- bug: "304347838"
-}
-
-flag {
- name: "register_nsd_offload_engine"
- namespace: "android_core_networking"
- description: "The flag controls the access for registerOffloadEngine API in NsdManager"
- bug: "294777050"
-}
+# This file contains aconfig flags used from platform code
+# Flags used for module APIs must be in aconfig files under each modules
flag {
name: "ipsec_transform_state"
- namespace: "android_core_networking_ipsec"
+ namespace: "core_networking_ipsec"
description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
bug: "308011229"
}
diff --git a/core/java/android/os/incremental/OWNERS b/core/java/android/os/incremental/OWNERS
index 47eee6406206..a925564fbbb6 100644
--- a/core/java/android/os/incremental/OWNERS
+++ b/core/java/android/os/incremental/OWNERS
@@ -1,6 +1,4 @@
# Bug component: 554432
-alexbuy@google.com
-schfan@google.com
-toddke@google.com
+include /PACKAGE_MANAGER_OWNERS
+
zyy@google.com
-patb@google.com
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index 92e49676cbae..bcdb98282679 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -35,6 +35,7 @@ import android.os.Vibrator.VibrationIntensity;
import android.util.IndentingPrintWriter;
import java.io.PrintWriter;
+import java.util.Arrays;
/**
* List of device-specific internal vibration configuration loaded from platform config.xml.
@@ -51,6 +52,8 @@ public class VibrationConfig {
private final float mHapticChannelMaxVibrationAmplitude;
private final int mRampStepDurationMs;
private final int mRampDownDurationMs;
+ private final int mRequestVibrationParamsTimeoutMs;
+ private final int[] mRequestVibrationParamsForUsages;
private final boolean mIgnoreVibrationsOnWirelessCharger;
@@ -75,6 +78,10 @@ public class VibrationConfig {
com.android.internal.R.integer.config_vibrationWaveformRampDownDuration, 0);
mRampStepDurationMs = loadInteger(resources,
com.android.internal.R.integer.config_vibrationWaveformRampStepDuration, 0);
+ mRequestVibrationParamsTimeoutMs = loadInteger(resources,
+ com.android.internal.R.integer.config_requestVibrationParamsTimeout, 0);
+ mRequestVibrationParamsForUsages = loadIntArray(resources,
+ com.android.internal.R.array.config_requestVibrationParamsForUsages);
mIgnoreVibrationsOnWirelessCharger = loadBoolean(resources,
com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
@@ -115,6 +122,10 @@ public class VibrationConfig {
return res != null ? res.getBoolean(resId) : defaultValue;
}
+ private static int[] loadIntArray(@Nullable Resources res, int resId) {
+ return res != null ? res.getIntArray(resId) : new int[0];
+ }
+
/**
* Return the maximum amplitude the vibrator can play using the audio haptic channels.
*
@@ -140,6 +151,23 @@ public class VibrationConfig {
}
/**
+ * The duration, in milliseconds, that the vibrator control service will wait for new
+ * vibration params.
+ */
+ public int getRequestVibrationParamsTimeoutMs() {
+ return Math.max(mRequestVibrationParamsTimeoutMs, 0);
+ }
+
+ /**
+ * The list of usages that should request vibration params before they are played. These
+ * usages don't have strong latency requirements, e.g. ringtone and notification, and can be
+ * slightly delayed.
+ */
+ public int[] getRequestVibrationParamsForUsages() {
+ return mRequestVibrationParamsForUsages;
+ }
+
+ /**
* The duration, in milliseconds, that should be applied to convert vibration effect's
* {@link android.os.vibrator.RampSegment} to a {@link android.os.vibrator.StepSegment} on
* devices without PWLE support.
@@ -204,6 +232,9 @@ public class VibrationConfig {
+ ", mDefaultMediaIntensity=" + mDefaultMediaVibrationIntensity
+ ", mDefaultNotificationIntensity=" + mDefaultNotificationVibrationIntensity
+ ", mDefaultRingIntensity=" + mDefaultRingVibrationIntensity
+ + ", mRequestVibrationParamsTimeoutMs=" + mRequestVibrationParamsTimeoutMs
+ + ", mRequestVibrationParamsForUsages=" + Arrays.toString(
+ getRequestVibrationParamsForUsagesNames())
+ "}";
}
@@ -220,4 +251,14 @@ public class VibrationConfig {
pw.println("rampDownDurationMs = " + mRampDownDurationMs);
pw.decreaseIndent();
}
+
+ private String[] getRequestVibrationParamsForUsagesNames() {
+ int usagesCount = mRequestVibrationParamsForUsages.length;
+ String[] names = new String[usagesCount];
+ for (int i = 0; i < usagesCount; i++) {
+ names[i] = VibrationAttributes.usageToString(mRequestVibrationParamsForUsages[i]);
+ }
+
+ return names;
+ }
}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 5d6dfc760b02..7d127ad660c5 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -2102,7 +2102,7 @@ public class CallLog {
PhoneAccountHandle accountHandle) {
TelecomManager tm = null;
try {
- tm = TelecomManager.from(context);
+ tm = context.getSystemService(TelecomManager.class);
} catch (UnsupportedOperationException e) {
if (VERBOSE_LOG) {
Log.v(LOG_TAG, "No TelecomManager found to get account address.");
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index af688e92fb36..2841dc05b164 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3202,7 +3202,7 @@ public final class Telephony {
/**
* The infrastructure bitmask which the APN can be used on. For example, some APNs can only
* be used when the device is on cellular, on satellite, or both. The default value is
- * 1 (INFRASTRUCTURE_CELLULAR).
+ * 3 (INFRASTRUCTURE_CELLULAR | INFRASTRUCTURE_SATELLITE).
*
* <P>Type: INTEGER</P>
* @hide
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index 533d459e532b..8bd6c858be7c 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -1,7 +1,7 @@
# Bug component: 36824
brambonne@google.com
-brufino@google.com
+eranm@google.com
jeffv@google.com
per-file *NetworkSecurityPolicy.java = file:net/OWNERS
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 94c8eaf13ccb..783f3b7aa64b 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -26,7 +26,6 @@ import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
-import android.graphics.fonts.FontFamily.Builder.VariableFontFamilyType;
import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontVariationAxis;
import android.icu.util.ULocale;
@@ -240,6 +239,23 @@ public final class FontConfig implements Parcelable {
private final @IntRange(from = 0) int mIndex;
private final @NonNull String mFontVariationSettings;
private final @Nullable String mFontFamilyName;
+ private final @VarTypeAxes int mVarTypeAxes;
+
+ /** @hide */
+ @Retention(SOURCE)
+ @IntDef(prefix = { "VAR_TYPE_AXES_" }, value = {
+ VAR_TYPE_AXES_NONE,
+ VAR_TYPE_AXES_WGHT,
+ VAR_TYPE_AXES_ITAL,
+ })
+ public @interface VarTypeAxes {}
+
+ /** @hide */
+ public static final int VAR_TYPE_AXES_NONE = 0;
+ /** @hide */
+ public static final int VAR_TYPE_AXES_WGHT = 1;
+ /** @hide */
+ public static final int VAR_TYPE_AXES_ITAL = 2;
/**
* Construct a Font instance.
@@ -248,7 +264,8 @@ public final class FontConfig implements Parcelable {
*/
public Font(@NonNull File file, @Nullable File originalFile, @NonNull String postScriptName,
@NonNull FontStyle style, @IntRange(from = 0) int index,
- @NonNull String fontVariationSettings, @Nullable String fontFamilyName) {
+ @NonNull String fontVariationSettings, @Nullable String fontFamilyName,
+ @VarTypeAxes int varTypeAxes) {
mFile = file;
mOriginalFile = originalFile;
mPostScriptName = postScriptName;
@@ -256,6 +273,7 @@ public final class FontConfig implements Parcelable {
mIndex = index;
mFontVariationSettings = fontVariationSettings;
mFontFamilyName = fontFamilyName;
+ mVarTypeAxes = varTypeAxes;
}
@Override
@@ -273,6 +291,7 @@ public final class FontConfig implements Parcelable {
dest.writeInt(mIndex);
dest.writeString8(mFontVariationSettings);
dest.writeString8(mFontFamilyName);
+ dest.writeInt(mVarTypeAxes);
}
public static final @NonNull Creator<Font> CREATOR = new Creator<Font>() {
@@ -288,9 +307,10 @@ public final class FontConfig implements Parcelable {
int index = source.readInt();
String varSettings = source.readString8();
String fallback = source.readString8();
+ int varTypeAxes = source.readInt();
return new Font(path, originalPath, postScriptName, new FontStyle(weight, slant),
- index, varSettings, fallback);
+ index, varSettings, fallback, varTypeAxes);
}
@Override
@@ -366,6 +386,15 @@ public final class FontConfig implements Parcelable {
}
/**
+ * Returns the list of supported axes tags for variable family type resolution.
+ *
+ * @hide
+ */
+ public @VarTypeAxes int getVarTypeAxes() {
+ return mVarTypeAxes;
+ }
+
+ /**
* Returns the list of axes associated to this font.
* @deprecated Use getFontVariationSettings
* @hide
@@ -408,13 +437,14 @@ public final class FontConfig implements Parcelable {
&& Objects.equals(mOriginalFile, font.mOriginalFile)
&& Objects.equals(mStyle, font.mStyle)
&& Objects.equals(mFontVariationSettings, font.mFontVariationSettings)
- && Objects.equals(mFontFamilyName, font.mFontFamilyName);
+ && Objects.equals(mFontFamilyName, font.mFontFamilyName)
+ && mVarTypeAxes == font.mVarTypeAxes;
}
@Override
public int hashCode() {
return Objects.hash(mFile, mOriginalFile, mStyle, mIndex, mFontVariationSettings,
- mFontFamilyName);
+ mFontFamilyName, mVarTypeAxes);
}
@Override
@@ -426,6 +456,7 @@ public final class FontConfig implements Parcelable {
+ ", mIndex=" + mIndex
+ ", mFontVariationSettings='" + mFontVariationSettings + '\''
+ ", mFontFamilyName='" + mFontFamilyName + '\''
+ + ", mVarTypeAxes='" + mVarTypeAxes + '\''
+ '}';
}
}
@@ -549,7 +580,6 @@ public final class FontConfig implements Parcelable {
private final @NonNull List<Font> mFonts;
private final @NonNull LocaleList mLocaleList;
private final @Variant int mVariant;
- private final int mVariableFontFamilyType;
/** @hide */
@Retention(SOURCE)
@@ -589,11 +619,10 @@ public final class FontConfig implements Parcelable {
* @hide Only system server can create this instance and passed via IPC.
*/
public FontFamily(@NonNull List<Font> fonts, @NonNull LocaleList localeList,
- @Variant int variant, int variableFontFamilyType) {
+ @Variant int variant) {
mFonts = fonts;
mLocaleList = localeList;
mVariant = variant;
- mVariableFontFamilyType = variableFontFamilyType;
}
/**
@@ -644,20 +673,6 @@ public final class FontConfig implements Parcelable {
return mVariant;
}
- /**
- * Returns the font family type.
- *
- * @see Builder#VARIABLE_FONT_FAMILY_TYPE_NONE
- * @see Builder#VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL
- * @see Builder#VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY
- * @see Builder#VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT
- * @hide
- * @return variable font family type.
- */
- public @VariableFontFamilyType int getVariableFontFamilyType() {
- return mVariableFontFamilyType;
- }
-
@Override
public int describeContents() {
return 0;
@@ -668,7 +683,6 @@ public final class FontConfig implements Parcelable {
dest.writeTypedList(mFonts, flags);
dest.writeString8(mLocaleList.toLanguageTags());
dest.writeInt(mVariant);
- dest.writeInt(mVariableFontFamilyType);
}
public static final @NonNull Creator<FontFamily> CREATOR = new Creator<FontFamily>() {
@@ -679,10 +693,8 @@ public final class FontConfig implements Parcelable {
source.readTypedList(fonts, Font.CREATOR);
String langTags = source.readString8();
int variant = source.readInt();
- int varFamilyType = source.readInt();
- return new FontFamily(fonts, LocaleList.forLanguageTags(langTags), variant,
- varFamilyType);
+ return new FontFamily(fonts, LocaleList.forLanguageTags(langTags), variant);
}
@Override
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 6e45fea930d2..f3e0ea780182 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -96,3 +96,10 @@ flag {
description: "A feature flag for fixing the crash while delete text in insert mode."
bug: "314254153"
}
+
+flag {
+ name: "insert_mode_not_update_selection"
+ namespace: "text"
+ description: "Fix that InputService#onUpdateSelection is not called when insert mode gesture is performed."
+ bug: "300850862"
+}
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index c0ceb9ea8204..9ecb4cbb283d 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -217,7 +217,6 @@ public final class Slog {
* @see Log#wtf(String, String)
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @android.ravenwood.annotation.RavenwoodThrow
public static int wtf(@Nullable String tag, @NonNull String msg) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, false, true);
}
@@ -263,7 +262,6 @@ public final class Slog {
*
* @see Log#wtf(String, Throwable)
*/
- @android.ravenwood.annotation.RavenwoodThrow
public static int wtf(@Nullable String tag, @Nullable Throwable tr) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, tr.getMessage(), tr, false, true);
}
@@ -284,7 +282,6 @@ public final class Slog {
* @see Log#wtf(String, String, Throwable)
*/
@UnsupportedAppUsage
- @android.ravenwood.annotation.RavenwoodThrow
public static int wtf(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, tr, false, true);
}
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 064bc6947fc4..35b137a322e3 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -314,13 +314,20 @@ public class FocusFinder {
if (count < 2) {
return null;
}
+ View next = null;
+ final boolean[] looped = new boolean[1];
switch (direction) {
case View.FOCUS_FORWARD:
- return getNextFocusable(focused, focusables, count);
+ next = getNextFocusable(focused, focusables, count, looped);
+ break;
case View.FOCUS_BACKWARD:
- return getPreviousFocusable(focused, focusables, count);
+ next = getPreviousFocusable(focused, focusables, count, looped);
+ break;
}
- return focusables.get(count - 1);
+ if (root != null && root.mAttachInfo != null && root == root.getRootView()) {
+ root.mAttachInfo.mNextFocusLooped = looped[0];
+ }
+ return next != null ? next : focusables.get(count - 1);
}
private void setFocusBottomRight(ViewGroup root, Rect focusedRect) {
@@ -375,7 +382,8 @@ public class FocusFinder {
return closest;
}
- private static View getNextFocusable(View focused, ArrayList<View> focusables, int count) {
+ private static View getNextFocusable(View focused, ArrayList<View> focusables, int count,
+ boolean[] outLooped) {
if (count < 2) {
return null;
}
@@ -385,10 +393,12 @@ public class FocusFinder {
return focusables.get(position + 1);
}
}
+ outLooped[0] = true;
return focusables.get(0);
}
- private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) {
+ private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count,
+ boolean[] outLooped) {
if (count < 2) {
return null;
}
@@ -398,6 +408,7 @@ public class FocusFinder {
return focusables.get(position - 1);
}
}
+ outLooped[0] = true;
return focusables.get(count - 1);
}
diff --git a/core/java/android/view/HdrRenderState.java b/core/java/android/view/HdrRenderState.java
index 2fbbf48dff77..eadc507116de 100644
--- a/core/java/android/view/HdrRenderState.java
+++ b/core/java/android/view/HdrRenderState.java
@@ -31,9 +31,11 @@ class HdrRenderState implements Consumer<Display> {
private final ViewRootImpl mViewRoot;
+ private boolean mIsHdrEnabled = false;
private boolean mIsListenerRegistered = false;
private boolean mUpdateHdrSdrRatioInfo = false;
private float mDesiredHdrSdrRatio = 1f;
+ private float mTargetDesiredHdrSdrRatio = 1f;
private float mTargetHdrSdrRatio = 1f;
private float mRenderHdrSdrRatio = 1f;
private float mPreviousRenderRatio = 1f;
@@ -50,7 +52,7 @@ class HdrRenderState implements Consumer<Display> {
}
boolean isHdrEnabled() {
- return mDesiredHdrSdrRatio >= 1.01f;
+ return mIsHdrEnabled;
}
void stopListening() {
@@ -75,9 +77,7 @@ class HdrRenderState implements Consumer<Display> {
final float maxStep = timeDelta * TRANSITION_PER_MS;
mLastUpdateMillis = frameTimeMillis;
if (hasUpdate && FLAG_ANIMATE_ENABLED) {
- if (mTargetHdrSdrRatio == 1.0f) {
- mPreviousRenderRatio = mTargetHdrSdrRatio;
- } else {
+ if (isHdrEnabled()) {
float delta = mTargetHdrSdrRatio - mPreviousRenderRatio;
if (delta > maxStep) {
mRenderHdrSdrRatio = mPreviousRenderRatio + maxStep;
@@ -85,6 +85,19 @@ class HdrRenderState implements Consumer<Display> {
mViewRoot.invalidate();
}
mPreviousRenderRatio = mRenderHdrSdrRatio;
+
+ if (mTargetDesiredHdrSdrRatio < mDesiredHdrSdrRatio) {
+ mDesiredHdrSdrRatio = Math.max(mTargetDesiredHdrSdrRatio,
+ mDesiredHdrSdrRatio - maxStep);
+ if (mDesiredHdrSdrRatio != mTargetDesiredHdrSdrRatio) {
+ mUpdateHdrSdrRatioInfo = true;
+ mViewRoot.invalidate();
+ }
+ }
+
+ } else {
+ mPreviousRenderRatio = mTargetHdrSdrRatio;
+ mDesiredHdrSdrRatio = mTargetDesiredHdrSdrRatio;
}
}
return hasUpdate;
@@ -99,15 +112,23 @@ class HdrRenderState implements Consumer<Display> {
}
void forceUpdateHdrSdrRatio() {
- mTargetHdrSdrRatio = Math.min(mDesiredHdrSdrRatio, mViewRoot.mDisplay.getHdrSdrRatio());
+ if (isHdrEnabled()) {
+ mTargetHdrSdrRatio = Math.min(mDesiredHdrSdrRatio,
+ mViewRoot.mDisplay.getHdrSdrRatio());
+ } else {
+ mTargetHdrSdrRatio = 1.0f;
+ }
mUpdateHdrSdrRatioInfo = true;
}
- void setDesiredHdrSdrRatio(float desiredRatio) {
+ void setDesiredHdrSdrRatio(boolean isHdrEnabled, float desiredRatio) {
+ mIsHdrEnabled = isHdrEnabled;
mLastUpdateMillis = SystemClock.uptimeMillis();
- // TODO: When decreasing the desired ratio we need to animate it downwards
- if (desiredRatio != mDesiredHdrSdrRatio) {
- mDesiredHdrSdrRatio = desiredRatio;
+ if (desiredRatio != mTargetDesiredHdrSdrRatio) {
+ mTargetDesiredHdrSdrRatio = desiredRatio;
+ if (mTargetDesiredHdrSdrRatio > mDesiredHdrSdrRatio || !FLAG_ANIMATE_ENABLED) {
+ mDesiredHdrSdrRatio = mTargetDesiredHdrSdrRatio;
+ }
forceUpdateHdrSdrRatio();
mViewRoot.invalidate();
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index c6601e8d3085..1ee9509b116a 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -19,6 +19,7 @@ package android.view;
import static android.os.IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
import static android.view.Display.INVALID_DISPLAY;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -31,6 +32,8 @@ import android.util.Log;
import android.util.SparseIntArray;
import android.view.KeyCharacterMap.KeyData;
+import com.android.hardware.input.Flags;
+
import java.util.concurrent.TimeUnit;
/**
@@ -384,7 +387,13 @@ public class KeyEvent extends InputEvent implements Parcelable {
public static final int KEYCODE_META_RIGHT = 118;
/** Key code constant: Function modifier key. */
public static final int KEYCODE_FUNCTION = 119;
- /** Key code constant: System Request / Print Screen key. */
+ /**
+ * Key code constant: System Request / Print Screen key.
+ *
+ * This key is sent to the app first and only if the app doesn't handle it, the framework
+ * handles it (to take a screenshot), unlike {@code KEYCODE_TAKE_SCREENSHOT} which is
+ * fully handled by the framework.
+ */
public static final int KEYCODE_SYSRQ = 120;
/** Key code constant: Break / Pause key. */
public static final int KEYCODE_BREAK = 121;
@@ -921,14 +930,25 @@ public class KeyEvent extends InputEvent implements Parcelable {
* User customizable key #4.
*/
public static final int KEYCODE_MACRO_4 = 316;
-
+ /** Key code constant: To open emoji picker */
+ @FlaggedApi(Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE)
+ public static final int KEYCODE_EMOJI_PICKER = 317;
+ /**
+ * Key code constant: To take a screenshot
+ *
+ * This key is fully handled by the framework and will not be sent to the foreground app,
+ * unlike {@code KEYCODE_SYSRQ} which is sent to the app first and only if the app
+ * doesn't handle it, the framework handles it (to take a screenshot).
+ */
+ @FlaggedApi(Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE)
+ public static final int KEYCODE_SCREENSHOT = 318;
/**
* Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent.
* @hide
*/
@TestApi
- public static final int LAST_KEYCODE = KEYCODE_MACRO_4;
+ public static final int LAST_KEYCODE = KEYCODE_SCREENSHOT;
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1b22fda9c31a..0d2c2ccce122 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -31336,6 +31336,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final ArrayList<View> mTempArrayList = new ArrayList<View>(24);
/**
+ * Indicates if the next focus will be looped back to the first focusable view of the entire
+ * hierarchy when finding in the direction of {@link #FOCUS_FORWARD} or to the last
+ * focusable view when finding in the direction of {@link #FOCUS_BACKWARD}.
+ */
+ boolean mNextFocusLooped = false;
+
+ /**
* The id of the window for accessibility purposes.
*/
int mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7bc832ef9e3f..ae9c10986a82 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -399,6 +399,8 @@ public final class ViewRootImpl implements ViewParent,
@Nullable
private ContentObserver mForceInvertObserver;
+ private static final int INVALID_VALUE = Integer.MIN_VALUE;
+ private int mForceInvertEnabled = INVALID_VALUE;
/**
* Callback for notifying about global configuration changes.
*/
@@ -1604,6 +1606,23 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ private boolean isForceInvertEnabled() {
+ if (mForceInvertEnabled == INVALID_VALUE) {
+ reloadForceInvertEnabled();
+ }
+ return mForceInvertEnabled == 1;
+ }
+
+ private void reloadForceInvertEnabled() {
+ if (forceInvertColor()) {
+ mForceInvertEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ /* def= */ 0,
+ UserHandle.myUserId());
+ }
+ }
+
/**
* Register any kind of listeners if setView was success.
*/
@@ -1630,6 +1649,7 @@ public final class ViewRootImpl implements ViewParent,
mForceInvertObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
+ reloadForceInvertEnabled();
updateForceDarkMode();
}
};
@@ -1850,16 +1870,11 @@ public final class ViewRootImpl implements ViewParent,
@VisibleForTesting
public @ForceDarkType.ForceDarkTypeDef int determineForceDarkType() {
if (forceInvertColor()) {
- boolean isForceInvertEnabled = Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
- /* def= */ 0,
- UserHandle.myUserId()) == 1;
// Force invert ignores all developer opt-outs.
// We also ignore dark theme, since the app developer can override the user's preference
// for dark mode in configuration.uiMode. Instead, we assume that the force invert
// setting will be enabled at the same time dark theme is in the Settings app.
- if (isForceInvertEnabled) {
+ if (isForceInvertEnabled()) {
return ForceDarkType.FORCE_INVERT_COLOR_DARK;
}
}
@@ -5809,9 +5824,11 @@ public final class ViewRootImpl implements ViewParent,
if (mAttachInfo.mThreadedRenderer == null) {
return;
}
- if ((colorMode == ActivityInfo.COLOR_MODE_HDR || colorMode == ActivityInfo.COLOR_MODE_HDR10)
- && !mDisplay.isHdrSdrRatioAvailable()) {
+ boolean isHdr = colorMode == ActivityInfo.COLOR_MODE_HDR
+ || colorMode == ActivityInfo.COLOR_MODE_HDR10;
+ if (isHdr && !mDisplay.isHdrSdrRatioAvailable()) {
colorMode = ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT;
+ isHdr = false;
}
// TODO: Centralize this sanitization? Why do we let setting bad modes?
// Alternatively, can we just let HWUI figure it out? Do we need to care here?
@@ -5824,7 +5841,7 @@ public final class ViewRootImpl implements ViewParent,
desiredRatio = automaticRatio;
}
- mHdrRenderState.setDesiredHdrSdrRatio(desiredRatio);
+ mHdrRenderState.setDesiredHdrSdrRatio(isHdr, desiredRatio);
}
@Override
@@ -7266,8 +7283,18 @@ public final class ViewRootImpl implements ViewParent,
if (direction != 0) {
View focused = mView.findFocus();
if (focused != null) {
+ mAttachInfo.mNextFocusLooped = false;
View v = focused.focusSearch(direction);
if (v != null && v != focused) {
+ if (mAttachInfo.mNextFocusLooped) {
+ // The next focus is looped. Let's try to move the focus to the adjacent
+ // window. Note: we still need to move the focus in this window
+ // regardless of what moveFocusToAdjacentWindow returns, so the focus
+ // can be looped back from the focus in the adjacent window to next
+ // focus of this window.
+ moveFocusToAdjacentWindow(direction);
+ }
+
// do the math the get the interesting rect
// of previous focused into the coord system of
// newly focused view
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index ef1bf5a5c548..146b576a8d1f 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -2424,7 +2424,7 @@ public final class AccessibilityManager {
}
}
try {
- service.attachAccessibilityOverlayToDisplay_enforcePermission(
+ service.attachAccessibilityOverlayToDisplay(
displayId, surfaceControl);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 1c5d29e0ff1e..eca15869f581 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -139,5 +139,5 @@ interface IAccessibilityManager {
WindowTransformationSpec getWindowTransformationSpec(int windowId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
- void attachAccessibilityOverlayToDisplay_enforcePermission(int displayId, in SurfaceControl surfaceControl);
+ void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);
}
diff --git a/core/java/android/webkit/flags.aconfig b/core/java/android/webkit/flags.aconfig
new file mode 100644
index 000000000000..6938b29e78e9
--- /dev/null
+++ b/core/java/android/webkit/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.webkit"
+
+flag {
+ name: "update_service_ipc_wrapper"
+ namespace: "webview"
+ description: "New API: proper wrapper for IWebViewUpdateService"
+ bug: "319292658"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0654addae2ad..2433bd865e73 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -7597,14 +7597,6 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
- * Append additional instructions to this {@link DrawInstructions} object.
- */
- @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
- public void appendInstructions(@NonNull final byte[] instructions) {
- mInstructions.add(instructions);
- }
-
- /**
* Builder class for {@link DrawInstructions} objects.
*/
@FlaggedApi(FLAG_DRAW_DATA_PARCEL)
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index e812f858b9fd..90d51402f5b1 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -243,6 +243,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastMath;
import com.android.internal.util.Preconditions;
+import com.android.text.flags.Flags;
import libcore.util.EmptyArray;
@@ -539,7 +540,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// System wide time for last cut, copy or text changed action.
static long sLastCutCopyOrTextChangedTime;
-
private ColorStateList mTextColor;
private ColorStateList mHintTextColor;
private ColorStateList mLinkTextColor;
@@ -2857,7 +2857,38 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (updateText) {
- setText(mText);
+ if (Flags.insertModeNotUpdateSelection()) {
+ // Update the transformation text.
+ if (mTransformation == null) {
+ mTransformed = mText;
+ } else {
+ mTransformed = mTransformation.getTransformation(mText, this);
+ }
+ if (mTransformed == null) {
+ // Should not happen if the transformation method follows the non-null
+ // postcondition.
+ mTransformed = "";
+ }
+ final boolean isOffsetMapping = mTransformed instanceof OffsetMapping;
+
+ // If the mText is a Spannable and the new TransformationMethod needs to listen to
+ // its updates, apply the watcher on it.
+ if (mTransformation != null && mText instanceof Spannable
+ && (!mAllowTransformationLengthChange || isOffsetMapping)) {
+ Spannable sp = (Spannable) mText;
+ final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0;
+ sp.setSpan(mTransformation, 0, mText.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE
+ | (priority << Spanned.SPAN_PRIORITY_SHIFT));
+ }
+ if (mLayout != null) {
+ nullLayouts();
+ requestLayout();
+ invalidate();
+ }
+ } else {
+ setText(mText);
+ }
}
if (hasPasswordTransformationMethod()) {
diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig
new file mode 100644
index 000000000000..9f0b7c3dfb02
--- /dev/null
+++ b/core/java/android/widget/flags/notification_widget_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.widget.flags"
+
+flag {
+ name: "notif_linearlayout_optimized"
+ namespace: "systemui"
+ description: "Enables notification specific LinearLayout optimization"
+ bug: "316110233"
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl b/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
index ae6ad326e7b1..94bbd72ee76f 100644
--- a/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
@@ -19,5 +19,5 @@ package com.android.internal.app;
// Iterface to observe op active changes
oneway interface IAppOpsActiveCallback {
void opActiveChanged(int op, int uid, String packageName, String attributionTag,
- boolean active, int attributionFlags, int attributionChainId);
+ int virtualDeviceId, boolean active, int attributionFlags, int attributionChainId);
}
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/core/java/com/android/internal/app/IAppOpsCallback.aidl
index 024ff66a67f6..3a9525c03161 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsCallback.aidl
@@ -19,5 +19,5 @@ package com.android.internal.app;
// This interface is also used by native code, so must
// be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsCallback.h
oneway interface IAppOpsCallback {
- void opChanged(int op, int uid, String packageName);
+ void opChanged(int op, int uid, String packageName, String persistentDeviceId);
}
diff --git a/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl b/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl
index f3759e091ed6..123f4f496304 100644
--- a/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl
@@ -18,5 +18,6 @@ package com.android.internal.app;
// Iterface to observe op note/checks of ops
oneway interface IAppOpsNotedCallback {
- void opNoted(int op, int uid, String packageName, String attributionTag, int flags, int mode);
+ void opNoted(int op, int uid, String packageName, String attributionTag, int virtualDeviceId,
+ int flags, int mode);
}
diff --git a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
index 06640cb4ee06..fdae37a5b211 100644
--- a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
@@ -18,6 +18,6 @@ package com.android.internal.app;
// Iterface to observe op starts
oneway interface IAppOpsStartedCallback {
- void opStarted(int op, int uid, String packageName, String attributionTag, int flags, int mode,
- int startedType, int attributionFlags, int attributionChainId);
+ void opStarted(int op, int uid, String packageName, String attributionTag, int virtualDeviceId,
+ int flags, int mode, int startedType, int attributionFlags, int attributionChainId);
}
diff --git a/core/java/com/android/internal/os/AndroidPrintStream.java b/core/java/com/android/internal/os/AndroidPrintStream.java
index a6e41ff02d12..bb388bbd0b4b 100644
--- a/core/java/com/android/internal/os/AndroidPrintStream.java
+++ b/core/java/com/android/internal/os/AndroidPrintStream.java
@@ -24,6 +24,7 @@ import android.util.Log;
*
* {@hide}
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
class AndroidPrintStream extends LoggingPrintStream {
private final int priority;
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 28b98d6fab06..cdac09796137 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -39,6 +39,7 @@ import dalvik.system.VMRuntime;
import libcore.content.type.MimeMap;
+import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@@ -50,6 +51,7 @@ import java.util.logging.LogManager;
* public consumption.
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public class RuntimeInit {
final static String TAG = "AndroidRuntime";
final static boolean DEBUG = false;
@@ -67,7 +69,15 @@ public class RuntimeInit {
private static volatile ApplicationWtfHandler sDefaultApplicationWtfHandler;
+ /**
+ * Stored values of System.out and System.err before they've been replaced by
+ * redirectLogStreams(). Kept open here for other Ravenwood internals to use.
+ */
+ public static PrintStream sOut$ravenwood;
+ public static PrintStream sErr$ravenwood;
+
private static final native void nativeFinishInit();
+
private static final native void nativeSetExitWithoutCleanup(boolean exitWithoutCleanup);
private static int Clog_e(String tag, String msg, Throwable tr) {
@@ -385,6 +395,7 @@ public class RuntimeInit {
/**
* Redirect System.out and System.err to the Android log.
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static void redirectLogStreams() {
System.out.close();
System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
@@ -392,6 +403,17 @@ public class RuntimeInit {
System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
}
+ public static void redirectLogStreams$ravenwood() {
+ if (sOut$ravenwood == null) {
+ sOut$ravenwood = System.out;
+ System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
+ }
+ if (sErr$ravenwood == null) {
+ sErr$ravenwood = System.err;
+ System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
+ }
+ }
+
/**
* Report a serious error in the current process. May or may not cause
* the process to terminate (depends on system settings).
@@ -399,6 +421,7 @@ public class RuntimeInit {
* @param tag to record with the error
* @param t exception describing the error site and conditions
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static void wtf(String tag, Throwable t, boolean system) {
try {
boolean exit = false;
@@ -436,6 +459,11 @@ public class RuntimeInit {
}
}
+ public static void wtf$ravenwood(String tag, Throwable t, boolean system) {
+ // We've already emitted to logs, so there's nothing more to do here,
+ // as we don't have a DropBox pipeline configured
+ }
+
/**
* Set the default {@link ApplicationWtfHandler}, in case the ActivityManager is not ready yet.
*/
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 4e3b64c0d4b9..b4f9ee3b0b74 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2583,7 +2583,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
Color.TRANSPARENT);
}
- if (!targetPreQ && !mEdgeToEdgeEnforced) {
+ if (!targetPreQ) {
mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
R.styleable.Window_enforceStatusBarContrast, false);
mEnsureNavigationBarContrastWhenTransparent = a.getBoolean(
@@ -3966,9 +3966,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public void setStatusBarContrastEnforced(boolean ensureContrast) {
- if (mEdgeToEdgeEnforced) {
- return;
- }
mEnsureStatusBarContrastWhenTransparent = ensureContrast;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
@@ -3982,9 +3979,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public void setNavigationBarContrastEnforced(boolean enforceContrast) {
- if (mEdgeToEdgeEnforced) {
- return;
- }
mEnsureNavigationBarContrastWhenTransparent = enforceContrast;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 5351c6dbf94f..ed43b8190f93 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -151,8 +151,17 @@ oneway interface IStatusBar
* @param hidesStatusBar whether it is being hidden
*/
void setTopAppHidesStatusBar(boolean hidesStatusBar);
-
+ /**
+ * Add a tile to the Quick Settings Panel to the first item in the QS Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ */
void addQsTile(in ComponentName tile);
+ /**
+ * Add a tile to the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ * @param end if true, the tile will be added at the end. If false, at the beginning.
+ */
+ void addQsTileToFrontOrEnd(in ComponentName tile, boolean end);
void remQsTile(in ComponentName tile);
void setQsTiles(in String[] tiles);
void clickQsTile(in ComponentName tile);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 656cc3e2af71..3fc16833fc70 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -284,7 +284,6 @@ cc_library_shared_for_libandroid_runtime {
"libscrypt_static",
"libstatssocket_lazy",
"libskia",
- "libperfetto_client_experimental",
],
shared_libs: [
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 3413ededb889..969e47b6a803 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -25,6 +25,7 @@
#include <android_os_Parcel.h>
#include <audiomanager/AudioManager.h>
#include <jni.h>
+#include <media/AidlConversion.h>
#include <media/AudioContainers.h>
#include <media/AudioPolicy.h>
#include <media/AudioSystem.h>
@@ -66,6 +67,11 @@ static struct {
jmethodID toArray;
} gArrayListMethods;
+static jclass gIntArrayClass;
+static struct {
+ jmethodID add;
+} gIntArrayMethods;
+
static jclass gBooleanClass;
static jmethodID gBooleanCstor;
@@ -1607,6 +1613,48 @@ android_media_AudioSystem_listAudioPorts(JNIEnv *env, jobject clazz,
return jStatus;
}
+// From AudioDeviceInfo
+static const int GET_DEVICES_INPUTS = 0x0001;
+static const int GET_DEVICES_OUTPUTS = 0x0002;
+
+static int android_media_AudioSystem_getSupportedDeviceTypes(JNIEnv *env, jobject clazz,
+ jint direction, jobject jDeviceTypes) {
+ if (jDeviceTypes == NULL) {
+ ALOGE("%s NULL Device Types IntArray", __func__);
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+ if (!env->IsInstanceOf(jDeviceTypes, gIntArrayClass)) {
+ ALOGE("%s not an IntArray", __func__);
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+
+ // Convert AudioManager.GET_DEVICES_ flags to AUDIO_PORT_ROLE_ constants
+ audio_port_role_t role;
+ if (direction == GET_DEVICES_INPUTS) {
+ role = AUDIO_PORT_ROLE_SOURCE;
+ } else if (direction == GET_DEVICES_OUTPUTS) {
+ role = AUDIO_PORT_ROLE_SINK;
+ } else {
+ ALOGE("%s invalid direction : 0x%X", __func__, direction);
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+
+ std::vector<media::AudioPortFw> deviceList;
+ AudioSystem::listDeclaredDevicePorts(static_cast<media::AudioPortRole>(role), &deviceList);
+
+ // Walk the device list
+ for (const auto &device : deviceList) {
+ ConversionResult<audio_port_v7> result = aidl2legacy_AudioPortFw_audio_port_v7(device);
+
+ struct audio_port_v7 port = VALUE_OR_RETURN_STATUS(result);
+ assert(port.type == AUDIO_PORT_TYPE_DEVICE);
+
+ env->CallVoidMethod(jDeviceTypes, gIntArrayMethods.add, port.ext.device.type);
+ }
+
+ return AUDIO_JAVA_SUCCESS;
+}
+
static int
android_media_AudioSystem_createAudioPatch(JNIEnv *env, jobject clazz,
jobjectArray jPatches, jobjectArray jSources, jobjectArray jSinks)
@@ -3184,6 +3232,8 @@ static const JNINativeMethod gMethods[] =
android_media_AudioSystem_setAudioFlingerBinder),
MAKE_JNI_NATIVE_METHOD("listAudioPorts", "(Ljava/util/ArrayList;[I)I",
android_media_AudioSystem_listAudioPorts),
+ MAKE_JNI_NATIVE_METHOD("getSupportedDeviceTypes", "(ILandroid/util/IntArray;)I",
+ android_media_AudioSystem_getSupportedDeviceTypes),
MAKE_JNI_NATIVE_METHOD("createAudioPatch",
"([Landroid/media/AudioPatch;[Landroid/media/"
"AudioPortConfig;[Landroid/media/AudioPortConfig;)I",
@@ -3325,6 +3375,10 @@ int register_android_media_AudioSystem(JNIEnv *env)
gArrayListMethods.add = GetMethodIDOrDie(env, arrayListClass, "add", "(Ljava/lang/Object;)Z");
gArrayListMethods.toArray = GetMethodIDOrDie(env, arrayListClass, "toArray", "()[Ljava/lang/Object;");
+ jclass intArrayClass = FindClassOrDie(env, "android/util/IntArray");
+ gIntArrayClass = MakeGlobalRefOrDie(env, intArrayClass);
+ gIntArrayMethods.add = GetMethodIDOrDie(env, gIntArrayClass, "add", "(I)V");
+
jclass booleanClass = FindClassOrDie(env, "java/lang/Boolean");
gBooleanClass = MakeGlobalRefOrDie(env, booleanClass);
gBooleanCstor = GetMethodIDOrDie(env, booleanClass, "<init>", "(Z)V");
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index d71069866d89..16c3ca97aaed 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -25,7 +25,6 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h
index 4ddf1d8d4512..61a7654b79af 100644
--- a/core/jni/android_tracing_PerfettoDataSource.h
+++ b/core/jni/android_tracing_PerfettoDataSource.h
@@ -23,10 +23,10 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
+#include <map>
#include <sstream>
#include <thread>
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.cpp b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
index e659bf1c55e9..3fbd5b3da1ed 100644
--- a/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
@@ -25,7 +25,6 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.h b/core/jni/android_tracing_PerfettoDataSourceInstance.h
index d57765565d8a..ebb525981b38 100644
--- a/core/jni/android_tracing_PerfettoDataSourceInstance.h
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.h
@@ -23,7 +23,6 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
diff --git a/core/jni/android_tracing_PerfettoProducer.cpp b/core/jni/android_tracing_PerfettoProducer.cpp
index ce72f5893c19..f8c63c80f769 100644
--- a/core/jni/android_tracing_PerfettoProducer.cpp
+++ b/core/jni/android_tracing_PerfettoProducer.cpp
@@ -23,7 +23,6 @@
#include <perfetto/public/producer.h>
#include <perfetto/public/protos/trace/test_event.pzc.h>
#include <perfetto/public/protos/trace/trace_packet.pzc.h>
-#include <perfetto/tracing.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index d4a746290baa..d11166fd6187 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -203,8 +203,10 @@ static jlong android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject
if (parcel) {
bool isInitialized = parcel->readInt32();
if (isInitialized) {
- std::unique_ptr<InputChannel> inputChannel = std::make_unique<InputChannel>();
- inputChannel->readFromParcel(parcel);
+ android::os::InputChannelCore parcelableChannel;
+ parcelableChannel.readFromParcel(parcel);
+ std::unique_ptr<InputChannel> inputChannel =
+ InputChannel::create(std::move(parcelableChannel));
NativeInputChannel* nativeInputChannel =
new NativeInputChannel(std::move(inputChannel));
return reinterpret_cast<jlong>(nativeInputChannel);
@@ -228,7 +230,9 @@ static void android_view_InputChannel_nativeWriteToParcel(JNIEnv* env, jobject o
return;
}
parcel->writeInt32(1); // initialized
- nativeInputChannel->getInputChannel()->writeToParcel(parcel);
+ android::os::InputChannelCore parcelableChannel;
+ nativeInputChannel->getInputChannel()->copyTo(parcelableChannel);
+ parcelableChannel.writeToParcel(parcel);
}
static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj, jlong channel) {
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index f7d815283885..f93b3068a229 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -189,11 +189,11 @@ status_t NativeInputEventReceiver::reportTimeline(int32_t inputEventId, nsecs_t
void NativeInputEventReceiver::setFdEvents(int events) {
if (mFdEvents != events) {
mFdEvents = events;
- auto&& fd = mInputConsumer.getChannel()->getFd();
+ const int fd = mInputConsumer.getChannel()->getFd();
if (events) {
- mMessageQueue->getLooper()->addFd(fd.get(), 0, events, this, nullptr);
+ mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
} else {
- mMessageQueue->getLooper()->removeFd(fd.get());
+ mMessageQueue->getLooper()->removeFd(fd);
}
}
}
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 6bdf8214a1bf..323f7b63246c 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -102,8 +102,8 @@ NativeInputEventSender::~NativeInputEventSender() {
}
status_t NativeInputEventSender::initialize() {
- auto&& receiveFd = mInputPublisher.getChannel()->getFd();
- mMessageQueue->getLooper()->addFd(receiveFd.get(), 0, ALOOPER_EVENT_INPUT, this, NULL);
+ const int receiveFd = mInputPublisher.getChannel()->getFd();
+ mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL);
return OK;
}
@@ -112,7 +112,7 @@ void NativeInputEventSender::dispose() {
LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Disposing input event sender.";
}
- mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd().get());
+ mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd());
}
status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) {
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index a854e3626e78..381580b7628e 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -13,7 +13,7 @@ ogunwale@google.com
jjaggi@google.com
kwekua@google.com
roosa@google.com
-per-file package_item_info.proto = toddke@google.com,patb@google.com
+per-file package_item_info.proto = file:/PACKAGE_MANAGER_OWNERS
per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS
per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0171f584a838..5fad4c611961 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3188,6 +3188,14 @@
<permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
android:protectionLevel="signature|appop" />
+ <!-- Allows applications to access profiles with ACCESS_HIDDEN_PROFILES user property
+ <p>Protection level: normal
+ @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") -->
+ <permission android:name="android.permission.ACCESS_HIDDEN_PROFILES"
+ android:label="@string/permlab_accessHiddenProfile"
+ android:description="@string/permdesc_accessHiddenProfile"
+ android:protectionLevel="normal" />
+
<!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
<permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
android:protectionLevel="signature|role" />
@@ -7828,7 +7836,7 @@
android:protectionLevel="normal"/>
<!-- @FlaggedApi("android.app.job.backup_jobs_exemption")
- Gives applications whose <b>primary use case</b> is to backup or sync content increased
+ Gives applications with a <b>major use case</b> of backing-up or syncing content increased
job execution allowance in order to complete the related work. The jobs must have a valid
content URI trigger and network constraint set.
<p>This is a special access permission that can be revoked by the system or the user.
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index fe12f6ee7836..f3acab063bbf 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2024 The Android Open Source Project
+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.
@@ -20,103 +20,179 @@ Copyright (C) 2024 The Android Open Source Project
android:viewportWidth="512"
android:viewportHeight="512">
<path
- android:pathData="M256.22,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98s-24.52,-54.37 -11.33,-77.21c13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21s-103.26,178.86 -120.65,208.98c-17.39,30.12 -34.83,48.42 -61.2,48.42Z"
- android:strokeWidth="0">
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0">
<aapt:attr name="android:fillColor">
<gradient
- android:startX="56.22"
- android:startY="256"
- android:endX="456.22"
- android:endY="256"
+ android:startX="256"
+ android:startY="21.81"
+ android:endX="256"
+ android:endY="350.42"
android:type="linear">
<item android:offset="0" android:color="#FF073042"/>
<item android:offset="1" android:color="#FF073042"/>
</gradient>
</aapt:attr>
</path>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z"
+ android:fillColor="#3ddc84"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z"
+ android:fillColor="#3ddc84"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z"
+ android:fillColor="#3ddc84"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M171.92,216.82h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M369.04,337.63h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M330.82,273.31h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M220.14,238.94h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M293.34,349.25h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M161.05,254.24h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M378.92,192h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <group>
+ <clip-path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ <path
+ android:pathData="M137.87,323.7h8.08v8.08h-8.08z"
+ android:fillColor="#fff"/>
+ </group>
+ <path
+ android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"
+ android:strokeWidth="56.561"
+ android:fillColor="#00000000"
+ android:strokeColor="#f86734"/>
<path
- android:pathData="M198.85,168.57l2.03,-6.03c12.55,70.48 45.87,256.56 45.87,45.41 0,-0.88 0.65,-1.63 1.46,-1.63h11.45c0.81,0 1.46,0.75 1.46,1.63 -0.18,369.8 -62.28,-39.37 -62.28,-39.37Z"
- android:strokeWidth="0"
- android:fillColor="#3ddc84"/>
- <path
- android:pathData="M186.69,167.97l-23.69,41.03c-1.36,2.36 -0.55,5.37 1.8,6.73 2.36,1.36 5.37,0.55 6.73,-1.8l23.99,-41.55c38.76,17.38 83.1,17.38 121.86,0l23.99,41.55c1.41,2.33 4.44,3.07 6.77,1.66 2.26,-1.37 3.04,-4.28 1.76,-6.59l-23.69,-41.03c40.68,-22.12 68.5,-63.31 72.57,-111.97H114.11c4.07,48.65 31.89,89.85 72.57,111.97Z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M248.46,168.59L259.2,168.59A1.92,1.92 0,0 1,261.12 170.5L261.12,195.83A1.92,1.92 0,0 1,259.2 197.75L248.46,197.75A1.92,1.92 0,0 1,246.54 195.83L246.54,170.5A1.92,1.92 0,0 1,248.46 168.59z"
- android:strokeWidth="0"
- android:fillColor="#3ddc84"/>
- <path
- android:pathData="M248.32,152.92L259.34,152.92A1.78,1.78 0,0 1,261.12 154.7L261.12,158.43A1.78,1.78 0,0 1,259.34 160.21L248.32,160.21A1.78,1.78 0,0 1,246.54 158.43L246.54,154.7A1.78,1.78 0,0 1,248.32 152.92z"
- android:strokeWidth="0"
+ android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z"
android:fillColor="#3ddc84"/>
<path
- android:pathData="M159.03,176.91h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M373.41,158.93h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M112.1,129.34h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
- android:strokeWidth="0"
- android:fillColor="#fff"/>
- <path
- android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
- android:strokeWidth="0"
+ android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z"
android:fillColor="#fff"/>
<path
- android:pathData="M330.82,263.31h8.08v8.08h-8.08z"
- android:strokeWidth="0"
+ android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z"
android:fillColor="#fff"/>
<path
- android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
- android:strokeWidth="0"
+ android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z"
android:fillColor="#fff"/>
<path
- android:pathData="M224.18,216.82h8.08v8.08h-8.08z"
- android:strokeWidth="0"
+ android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z"
android:fillColor="#fff"/>
<path
- android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
- android:strokeWidth="0"
+ android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z"
android:fillColor="#fff"/>
<path
- android:pathData="M293.34,339.25h8.08v8.08h-8.08z"
- android:strokeWidth="0"
+ android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z"
android:fillColor="#fff"/>
<path
- android:pathData="M165.09,236.28h8.08v8.08h-8.08z"
- android:strokeWidth="0"
+ android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z"
android:fillColor="#fff"/>
<path
- android:pathData="M378.92,192h8.08v8.08h-8.08z"
- android:strokeWidth="0"
+ android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z"
android:fillColor="#fff"/>
<path
- android:pathData="M204.28,314.86h8.08v8.08h-8.08z"
- android:strokeWidth="0"
+ android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z"
android:fillColor="#fff"/>
- <path
- android:pathData="M253.83,118.47c-6.04,0 -10.93,4.76 -10.93,10.62v13.1c0,1.13 0.92,2.05 2.05,2.05h0c1.13,0 2.05,-0.92 2.05,-2.05v-3.53c0,-2.27 1.84,-4.1 4.1,-4.1h5.47c2.27,0 4.1,1.84 4.1,4.1v3.53c0,1.13 0.92,2.05 2.05,2.05s2.05,-0.92 2.05,-2.05v-13.1c0,-5.86 -4.9,-10.62 -10.93,-10.62Z"
- android:strokeWidth="0"
- android:fillColor="#3ddc84"/>
- <path
- android:pathData="M256,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98 -17.39,-30.12 -24.52,-54.37 -11.33,-77.21 13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21 -17.39,30.12 -103.26,178.86 -120.65,208.98 -17.39,30.12 -34.83,48.42 -61.2,48.42Z"
- android:strokeWidth="55"
- android:fillColor="#00000000"
- android:strokeColor="#f86733"/>
</vector>
+
diff --git a/core/res/res/drawable/ic_satellite_alt_24px.xml b/core/res/res/drawable/ic_satellite_alt_24px.xml
new file mode 100644
index 000000000000..f9ca7dc66798
--- /dev/null
+++ b/core/res/res/drawable/ic_satellite_alt_24px.xml
@@ -0,0 +1,25 @@
+<!--
+ 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="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M560,928L560,848Q677,848 758.5,766.5Q840,685 840,568L920,568Q920,643 891.5,708.5Q863,774 814.5,822.5Q766,871 700.5,899.5Q635,928 560,928ZM560,768L560,688Q610,688 645,653Q680,618 680,568L760,568Q760,651 701.5,709.5Q643,768 560,768ZM222,903Q207,903 192,897Q177,891 165,880L23,738Q12,726 6,711Q0,696 0,681Q0,665 6,650.5Q12,636 23,625L150,498Q173,475 207,474.5Q241,474 264,497L314,547L342,519L292,469Q269,446 269,413Q269,380 292,357L349,300Q372,277 405.5,277Q439,277 462,300L512,350L540,322L490,272Q467,249 467,215.5Q467,182 490,159L617,32Q629,20 644,14Q659,8 674,8Q689,8 703.5,14Q718,20 730,32L872,174Q884,185 889.5,199.5Q895,214 895,230Q895,245 889.5,260Q884,275 872,287L745,414Q722,437 688.5,437Q655,437 632,414L582,364L554,392L604,442Q627,465 626.5,498.5Q626,532 603,555L547,611Q524,634 490.5,634Q457,634 434,611L384,561L356,589L406,639Q429,662 428.5,696Q428,730 405,753L278,880Q267,891 252.5,897Q238,903 222,903ZM222,824Q222,824 222,824Q222,824 222,824L264,782L122,640L80,682Q80,682 80,682Q80,682 80,682L222,824ZM307,739L349,697Q349,697 349,697Q349,697 349,697L207,555Q207,555 207,555Q207,555 207,555L165,597L307,739ZM491,555Q491,555 491,555Q491,555 491,555L547,499Q547,499 547,499Q547,499 547,499L405,357Q405,357 405,357Q405,357 405,357L349,413Q349,413 349,413Q349,413 349,413L491,555ZM689,357Q689,357 689,357Q689,357 689,357L731,315L589,173L547,215Q547,215 547,215Q547,215 547,215L689,357ZM774,272L816,230Q816,230 816,230Q816,230 816,230L674,88Q674,88 674,88Q674,88 674,88L632,130L774,272ZM448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456Z"/>
+</vector>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ebb0d344d7eb..238f242537cb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -283,6 +283,20 @@
when there's no network connection. If the scan doesn't timeout, use zero -->
<integer name="config_radioScanningTimeout">0</integer>
+ <!-- The duration (in milliseconds) that the vibrator control service will wait for new
+ vibration params. -->
+ <integer name="config_requestVibrationParamsTimeout">50</integer>
+
+ <!-- Array containing the usages that should request vibration params before they are played.
+ These usages don't have strong latency requirements, e.g. ringtone and notification, and
+ can be slightly delayed. -->
+ <integer-array name="config_requestVibrationParamsForUsages">
+ <item>17</item> <!-- USAGE_ALARM -->
+ <item>33</item> <!-- USAGE_RINGTONE -->
+ <item>49</item> <!-- USAGE_NOTIFICATION -->
+ <item>65</item> <!-- USAGE_COMMUNICATION_REQUEST -->
+ </integer-array>
+
<!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION.
Please don't copy them, copy anything else. -->
@@ -3327,9 +3341,27 @@
<string name="config_carrierAppInstallDialogComponent" translatable="false"
>com.android.simappdialog/com.android.simappdialog.InstallCarrierAppActivity</string>
- <!-- Name of the dialog that is used to get or save an app credential -->
+ <!-- Name of the default framework dialog that is used to get or save an app credential.
+
+ This UI should be always launch-able and is used as a fallback when an oem replacement activity
+ (defined at config_oemCredentialManagerDialogComponent) is undefined / not found. -->
<string name="config_credentialManagerDialogComponent" translatable="false"
>com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string>
+ <!-- Whether to allow the credential selector activity to be replaced by an activity at
+ run-time (restricted to the privileged activity specified by
+ config_credentialSelectorActivityName).
+
+ When disabled, the fallback activity defined at
+ config_credentialManagerDialogComponent will be used instead. -->
+ <bool name="config_enableOemCredentialManagerDialogComponent" translatable="false">true</bool>
+ <!-- Fully qualified activity name providing the credential selector UI, that serves the
+ CredentialManager APIs.
+
+ Used only when config_enableOemCredentialManagerDialogComponent is true.
+
+ If the activity specified cannot be found or launched, then the fallback activity defined at
+ config_credentialManagerDialogComponent will be used instead. -->
+ <string name="config_oemCredentialManagerDialogComponent" translatable="false"></string>
<!-- Name of the broadcast receiver that is used to receive provider change events -->
<string name="config_credentialManagerReceiverComponent" translatable="false"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 558bae727415..be059f1abbf6 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1572,6 +1572,11 @@
<string name="permdesc_setWallpaper">Allows the app to set the system wallpaper.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_accessHiddenProfile">Access hidden profiles</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_accessHiddenProfile">Allows the app to access hidden profiles.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_setWallpaperHints">adjust your wallpaper size</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_setWallpaperHints">Allows the app to set the system wallpaper size hints.</string>
@@ -6386,4 +6391,14 @@ ul.</string>
<string name="redacted_notification_message"></string>
<!-- Notification action title used instead of a notification's normal title sensitive [CHAR_LIMIT=NOTIF_BODY] -->
<string name="redacted_notification_action_title"></string>
+
+ <!-- Satellite related messages -->
+ <!-- Notification title when satellite service is connected. -->
+ <string name="satellite_notification_title">Auto connected to satellite</string>
+ <!-- Notification summary when satellite service is connected. [CHAR LIMIT=NONE] -->
+ <string name="satellite_notification_summary">You can send and receive messages without a mobile or Wi-Fi network</string>
+ <!-- Invoke "What to expect" dialog of messaging application -->
+ <string name="satellite_notification_open_message">Open Messages</string>
+ <!-- Invoke Satellite setting activity of Settings -->
+ <string name="satellite_notification_how_it_works">How it works</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index dc2f74bb76fe..699c8ac3a431 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2080,6 +2080,8 @@
<java-symbol type="bool" name="config_ignoreVibrationsOnWirelessCharger" />
<java-symbol type="integer" name="config_vibrationWaveformRampDownDuration" />
<java-symbol type="integer" name="config_radioScanningTimeout" />
+ <java-symbol type="integer" name="config_requestVibrationParamsTimeout" />
+ <java-symbol type="array" name="config_requestVibrationParamsForUsages" />
<java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
<java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />
<java-symbol type="integer" name="config_screenBrightnessSettingDefault" />
@@ -2281,6 +2283,8 @@
<java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
<java-symbol type="string" name="config_carrierAppInstallDialogComponent" />
<java-symbol type="string" name="config_credentialManagerDialogComponent" />
+ <java-symbol type="bool" name="config_enableOemCredentialManagerDialogComponent" />
+ <java-symbol type="string" name="config_oemCredentialManagerDialogComponent" />
<java-symbol type="string" name="config_credentialManagerReceiverComponent" />
<java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
<java-symbol type="string" name="config_persistentDataPackageName" />
@@ -5325,4 +5329,11 @@
<java-symbol type="bool" name="config_showPercentageTextDuringRebootToUpdate" />
<java-symbol type="integer" name="config_minMillisBetweenInputUserActivityEvents" />
+
+ <!-- System notification for satellite service -->
+ <java-symbol type="string" name="satellite_notification_title" />
+ <java-symbol type="string" name="satellite_notification_summary" />
+ <java-symbol type="string" name="satellite_notification_open_message" />
+ <java-symbol type="string" name="satellite_notification_how_it_works" />
+ <java-symbol type="drawable" name="ic_satellite_alt_24px" />
</resources>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index f47679991418..513e022f8069 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -203,7 +203,6 @@ android_ravenwood_test {
"androidx.test.uiautomator_uiautomator",
"compatibility-device-util-axt",
"flag-junit",
- "mockito_ravenwood",
"platform-test-annotations",
"flag-junit",
"testng",
diff --git a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
index 9e8e2d6a2446..cd5deb6c4293 100644
--- a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
@@ -20,24 +20,33 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
+import android.app.IBackupAgent;
import android.app.backup.BackupAgent.IncludeExcludeRules;
import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.BackupAnnotations.OperationType;
import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
+import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.backup.Flags;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@@ -49,9 +58,12 @@ public class BackupAgentTest {
private static final UserHandle USER_HANDLE = new UserHandle(15);
private static final String DATA_TYPE_BACKED_UP = "test data type";
+ @Mock IBackupManager mIBackupManager;
@Mock FullBackup.BackupScheme mBackupScheme;
+ @Mock Context mContext;
- private BackupAgent mBackupAgent;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
@@ -67,11 +79,11 @@ public class BackupAgentTest {
excludePaths.add(path);
IncludeExcludeRules expectedRules = new IncludeExcludeRules(includePaths, excludePaths);
- mBackupAgent = getAgentForBackupDestination(BackupDestination.CLOUD);
+ BackupAgent backupAgent = getAgentForBackupDestination(BackupDestination.CLOUD);
when(mBackupScheme.maybeParseAndGetCanonicalExcludePaths()).thenReturn(excludePaths);
when(mBackupScheme.maybeParseAndGetCanonicalIncludePaths()).thenReturn(includePaths);
- IncludeExcludeRules rules = mBackupAgent.getIncludeExcludeRules(mBackupScheme);
+ IncludeExcludeRules rules = backupAgent.getIncludeExcludeRules(mBackupScheme);
assertThat(rules).isEqualTo(expectedRules);
}
@@ -137,6 +149,31 @@ public class BackupAgentTest {
0).getSuccessCount()).isEqualTo(1);
}
+ @Test
+ public void doRestoreFile_agentOverrideIgnoresFile_consumesAllBytesInBuffer() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_CLEAR_PIPE_AFTER_RESTORE_FILE);
+ BackupAgent agent = new TestRestoreIgnoringFullBackupAgent();
+ agent.attach(mContext);
+ agent.onCreate(USER_HANDLE, BackupDestination.CLOUD, OperationType.RESTORE);
+ IBackupAgent agentBinder = (IBackupAgent) agent.onBind();
+
+ ParcelFileDescriptor[] pipes = ParcelFileDescriptor.createPipe();
+ FileOutputStream writeSide = new FileOutputStream(
+ pipes[1].getFileDescriptor());
+ writeSide.write("Hello".getBytes(StandardCharsets.UTF_8));
+
+ agentBinder.doRestoreFile(pipes[0], /* length= */ 5, BackupAgent.TYPE_FILE,
+ FullBackup.FILES_TREE_TOKEN, /* path= */ "hello_file", /* mode= */
+ 0666, /* mtime= */ 12345, /* token= */ 6789, mIBackupManager);
+
+ try (FileInputStream in = new FileInputStream(pipes[0].getFileDescriptor())) {
+ assertThat(in.available()).isEqualTo(0);
+ } finally {
+ pipes[0].close();
+ pipes[1].close();
+ }
+ }
+
private BackupAgent getAgentForBackupDestination(@BackupDestination int backupDestination) {
BackupAgent agent = new TestFullBackupAgent();
agent.onCreate(USER_HANDLE, backupDestination);
@@ -144,7 +181,6 @@ public class BackupAgentTest {
}
private static class TestFullBackupAgent extends BackupAgent {
-
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
@@ -162,4 +198,14 @@ public class BackupAgentTest {
// Left empty as this is a full backup agent.
}
}
+
+ private static class TestRestoreIgnoringFullBackupAgent extends TestFullBackupAgent {
+
+ @Override
+ protected void onRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String path, long mode, long mtime)
+ throws IOException {
+ // Ignore the file and don't consume any data.
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java
index 4dd5889fdf5d..5f96c1789015 100644
--- a/core/tests/coretests/src/android/graphics/FontListParserTest.java
+++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java
@@ -22,14 +22,18 @@ import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
import static android.text.FontConfig.FontFamily.VARIANT_COMPACT;
import static android.text.FontConfig.FontFamily.VARIANT_DEFAULT;
import static android.text.FontConfig.FontFamily.VARIANT_ELEGANT;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
import android.graphics.fonts.FontCustomizationParser;
-import android.graphics.fonts.FontFamily;
import android.graphics.fonts.FontStyle;
+import android.graphics.fonts.SystemFonts;
import android.os.LocaleList;
import android.text.FontConfig;
import android.util.Xml;
@@ -64,9 +68,9 @@ public final class FontListParserTest {
FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
Collections.singletonList(new FontConfig.FontFamily(
Arrays.asList(new FontConfig.Font(new File("test.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif");
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -82,12 +86,11 @@ public final class FontListParserTest {
Arrays.asList(
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null),
+ 0, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", "serif")),
- LocaleList.forLanguageTags("en"), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ 0, "", "serif", FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.forLanguageTags("en"), VARIANT_DEFAULT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -103,9 +106,8 @@ public final class FontListParserTest {
Arrays.asList(
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null)),
- LocaleList.forLanguageTags("en"), VARIANT_COMPACT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ 0, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.forLanguageTags("en"), VARIANT_COMPACT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -121,9 +123,8 @@ public final class FontListParserTest {
Arrays.asList(
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null)),
- LocaleList.forLanguageTags("en"), VARIANT_ELEGANT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ 0, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.forLanguageTags("en"), VARIANT_ELEGANT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -140,13 +141,15 @@ public final class FontListParserTest {
FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("normal.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("weight.ttf"), null, "test",
- new FontStyle(100, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontStyle(100, FONT_SLANT_UPRIGHT), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("italic.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), 0, "", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif");
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -168,12 +171,13 @@ public final class FontListParserTest {
Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("test-VF.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "'wdth' 100.0,'wght' 200.0", null),
+ 0, "'wdth' 100.0,'wght' 200.0", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("test-VF.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "'wdth' 400.0,'wght' 700.0", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)),
+ 0, "'wdth' 400.0,'wght' 700.0", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)),
"sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -190,12 +194,11 @@ public final class FontListParserTest {
Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("test.ttc"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null),
+ 0, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("test.ttc"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 1, "", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)),
+ 1, "", null, FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)),
"sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -211,11 +214,12 @@ public final class FontListParserTest {
FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("test.ttc"), null, "foo",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE),
new FontConfig.Font(new File("test.ttc"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null)),
- LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif");
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -385,14 +389,81 @@ public final class FontListParserTest {
public void varFamilyType() throws Exception {
String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ "<familyset>"
- + " <family name='sans-serif' varFamilyType='1'>"
- + " <font>test.ttf</font>"
+ + " <family name='sans-serif'>"
+ + " <font supportedAxes='wght'>test.ttf</font>"
+ + " <font supportedAxes='ital'>test.ttf</font>"
+ + " <font supportedAxes='wght,ital'>test.ttf</font>"
+ " </family>"
+ "</familyset>";
FontConfig config = readFamilies(xml, true /* include non-existing font files */);
List<FontConfig.FontFamily> families = config.getFontFamilies();
assertThat(families.size()).isEqualTo(1); // legacy one should be ignored.
- assertThat(families.get(0).getVariableFontFamilyType()).isEqualTo(1);
+ assertThat(families.get(0).getFontList().get(0).getVarTypeAxes())
+ .isEqualTo(FontConfig.Font.VAR_TYPE_AXES_WGHT);
+ assertThat(families.get(0).getFontList().get(1).getVarTypeAxes())
+ .isEqualTo(FontConfig.Font.VAR_TYPE_AXES_ITAL);
+ assertThat(families.get(0).getFontList().get(2).getVarTypeAxes())
+ .isEqualTo(FontConfig.Font.VAR_TYPE_AXES_WGHT | FontConfig.Font.VAR_TYPE_AXES_ITAL);
+ }
+
+ @Test
+ public void varFamilyTypeRsolve() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " <font style='italic' supportedAxes='wght'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font supportedAxes='wght,ital'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font supportedAxes='ital'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font>test.ttf</font>"
+ + " </family>"
+ + "</familyset>";
+ FontConfig config = readFamilies(xml, true /* include non-existing font files */);
+ List<FontConfig.FontFamily> families = config.getFontFamilies();
+ assertThat(families.size()).isEqualTo(5);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(0), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(1), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(2), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(3), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_NONE);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(4), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_NONE);
+ }
+
+ @Test
+ public void varFamilyTypeRsolve_ForName() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " </family>"
+ + " <family name='serif'>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font style='normal' supportedAxes='wght'>test.ttf</font>"
+ + " <font fallbackFor='serif' supportedAxes='wght,ital'>test.ttf</font>"
+ + " </family>"
+ + "</familyset>";
+ FontConfig config = readFamilies(xml, true /* include non-existing font files */);
+ List<FontConfig.FontFamily> families = config.getFontFamilies();
+ assertThat(families.size()).isEqualTo(2);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(1), null))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY);
+ assertThat(SystemFonts.resolveVarFamilyType(families.get(1), "serif"))
+ .isEqualTo(VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL);
}
private FontConfig readFamilies(String xml, boolean allowNonExisting)
diff --git a/core/tests/coretests/src/android/util/LogTest.java b/core/tests/coretests/src/android/util/LogTest.java
index f9966a1cf65d..15caac93f9ae 100644
--- a/core/tests/coretests/src/android/util/LogTest.java
+++ b/core/tests/coretests/src/android/util/LogTest.java
@@ -37,6 +37,13 @@ public class LogTest {
private static final String LOG_TAG = "LogTest";
@Test
+ public void testWtf() {
+ Log.wtf(LOG_TAG, "Message");
+ Log.wtf(LOG_TAG, "Message", new Throwable("Throwable"));
+ Log.wtf(LOG_TAG, new Throwable("Throwable"));
+ }
+
+ @Test
@Ignore
public void testIsLoggable() {
// First clear any SystemProperty setting for our test key.
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 543d73bc34e8..5862711ae57c 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -450,13 +450,8 @@ public class RemoteViewsTest {
}
private RemoteViews.DrawInstructions getDrawInstructions() {
- final byte[] first = new byte[] {'f', 'i', 'r', 's', 't'};
- final byte[] second = new byte[] {'s', 'e', 'c', 'o', 'n', 'd'};
- final RemoteViews.DrawInstructions drawInstructions =
- new RemoteViews.DrawInstructions.Builder(
- Collections.singletonList(first)).build();
- drawInstructions.appendInstructions(second);
- return drawInstructions;
+ final byte[] bytes = new byte[] {'h', 'e', 'l', 'l', 'o'};
+ return new RemoteViews.DrawInstructions.Builder(Collections.singletonList(bytes)).build();
}
private RemoteViews createViewChained(int depth, String... texts) {
diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp
index 2ccee71c8ff7..f5563a710563 100644
--- a/core/tests/utiltests/Android.bp
+++ b/core/tests/utiltests/Android.bp
@@ -60,7 +60,6 @@ android_ravenwood_test {
static_libs: [
"androidx.annotation_annotation",
"androidx.test.rules",
- "mockito_ravenwood",
"frameworks-base-testutils",
"servicestests-utils",
],
diff --git a/core/tests/utiltests/src/android/util/SlogTest.java b/core/tests/utiltests/src/android/util/SlogTest.java
index 6f761e348dbe..738c6680bf60 100644
--- a/core/tests/utiltests/src/android/util/SlogTest.java
+++ b/core/tests/utiltests/src/android/util/SlogTest.java
@@ -44,4 +44,11 @@ public class SlogTest {
Slog.w(TAG, MSG, THROWABLE);
Slog.e(TAG, MSG, THROWABLE);
}
+
+ @Test
+ public void testWtf() {
+ Slog.wtf(TAG, MSG);
+ Slog.wtf(TAG, MSG, THROWABLE);
+ Slog.wtf(TAG, THROWABLE);
+ }
}
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index ea23aba16d12..245f21658b7a 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -1,3 +1,5 @@
+include /PACKAGE_MANAGER_OWNERS
+
alanstokes@google.com
cbrubaker@google.com
hackbod@android.com
@@ -6,11 +8,6 @@ jeffv@google.com
jsharkey@android.com
jsharkey@google.com
lorenzo@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
-toddke@android.com
-toddke@google.com
-patb@google.com
yamasani@google.com
per-file preinstalled-packages* = file:/MULTIUSER_OWNERS
diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml
index 1bd182f96bde..15ea15a9b4c1 100644
--- a/data/fonts/font_fallback.xml
+++ b/data/fonts/font_fallback.xml
@@ -11,12 +11,84 @@
effectively add 300 to the weight, this ensures that 900 is the bold
paired with the 500 weight, ensuring adequate contrast.
- TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+
+ The font_fallback.xml defines the list of font used by the system.
+
+ `familyset` node:
+ A `familyset` element must be a root node of the font_fallback.xml. No attributes are allowed
+ to `familyset` node.
+ The `familyset` node can contains `family` and `alias` nodes. Any other nodes will be ignored.
+
+ `family` node:
+ A `family` node defines a single font family definition.
+ A font family is a set of fonts for drawing text in various styles such as weight, slant.
+ There are three types of families, default family, named family and locale fallback family.
+
+ The default family is a special family node appeared the first node of the `familyset` node.
+ The default family is used as first priority fallback.
+ Only `name` attribute can be used for default family node. If the `name` attribute is
+ specified, This family will also works as named family.
+
+ The named family is a family that has name attribute. The named family defines a new fallback.
+ For example, if the name attribute is "serif", it creates serif fallback. Developers can
+ access the fallback by using Typeface#create API.
+ The named family can not have attribute other than `name` attribute. The `name` attribute
+ cannot be empty.
+
+ The locale fallback family is a font family that is used for fallback. The fallback family is
+ used when the named family or default family cannot be used. The locale fallback family can
+ have `lang` attribute and `variant` attribute. The `lang` attribute is an optional comma
+ separated BCP-47i language tag. The `variant` is an optional attribute that can be one one
+ `element`, `compact`. If a `variant` attribute is not specified, it is treated as default.
+
+ `alias` node:
+ An `alias` node defines a alias of named family with changing weight offset. An `alias` node
+ can have mandatory `name` and `to` attribute and optional `weight` attribute. This `alias`
+ defines new fallback that has the name of specified `name` attribute. The fallback list is
+ the same to the fallback that of the name specified with `to` attribute. If `weight` attribute
+ is specified, the base weight offset is shifted to the specified value. For example, if the
+ `weight` is 500, the output text is drawn with 500 of weight.
+
+ `font` node:
+ A `font` node defines a single font definition. There are two types of fonts, static font and
+ variable font.
+
+ A static font can have `weight`, `style`, `index` and `postScriptName` attributes. A `weight`
+ is a mandatory attribute that defines the weight of the font. Any number between 0 to 1000 is
+ valid. A `style` is a mandatory attribute that defines the style of the font. A 'style'
+ attribute can be `normal` or `italic`. An `index` is an optional attribute that defines the
+ index of the font collection. If this is not specified, it is treated as 0. If the font file
+ is not a font collection, this attribute is ignored. A `postScriptName` attribute is an
+ optional attribute. A PostScript name is used for identifying target of system font update.
+ If this is not specified, the system assumes the filename is same to PostScript name of the
+ font file. For example, if the font file is "Roboto-Regular.ttf", the system assume the
+ PostScript name of this font is "Roboto-Regular".
+
+ A variable font can be only defined for the variable font file. A variable font can have
+ `axis` child nodes for specifying axis values. A variable font can have all attribute of
+ static font and can have additional `supportedAxes` attribute. A `supportedAxes` attribute
+ is a comma separated supported axis tags. As of Android V, only `wght` and `ital` axes can
+ be specified.
+
+ If `supportedAxes` attribute is not specified, this `font` node works as static font of the
+ single instance of variable font specified with `axis` children.
+
+ If `supportedAxes` attribute is specified, the system dynamically create font instance for the
+ given weight and style value. If `wght` is specified in `supportedAxes` attribute the `weight`
+ attribute and `axis` child that has `wght` tag become optional and ignored because it is
+ determined by system at runtime. Similarly, if `ital` is specified in `supportedAxes`
+ attribute, the `style` attribute and `axis` child that has `ital` tag become optional and
+ ignored.
+
+ `axis` node:
+ An `axis` node defines a font variation value for a tag. An `axis` node can have two mandatory
+ attributes, `tag` and `value`. If the font is variable font and the same tag `axis` node is
+ specified in `supportedAxes` attribute, the style value works like a default instance.
-->
-<familyset version="23">
+<familyset>
<!-- first font is default -->
- <family name="sans-serif" varFamilyType="2">
- <font>Roboto-Regular.ttf
+ <family name="sans-serif">
+ <font supportedAxes="wght,ital">Roboto-Regular.ttf
<axis tag="wdth" stylevalue="100" />
</font>
</family>
@@ -32,8 +104,8 @@
<alias name="tahoma" to="sans-serif" />
<alias name="verdana" to="sans-serif" />
- <family name="sans-serif-condensed" varFamilyType="2">
- <font>Roboto-Regular.ttf
+ <family name="sans-serif-condensed">
+ <font supportedAxes="wght,ital">Roboto-Regular.ttf
<axis tag="wdth" stylevalue="75" />
</font>
</family>
@@ -72,8 +144,8 @@
<font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
</family>
- <family name="cursive" varFamilyType="1">
- <font>DancingScript-Regular.ttf</font>
+ <family name="cursive">
+ <font supportedAxes="wght">DancingScript-Regular.ttf</font>
</family>
<family name="sans-serif-smallcaps">
@@ -90,8 +162,8 @@
</family>
<alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
- <family name="roboto-flex" varFamilyType="2">
- <font>RobotoFlex-Regular.ttf
+ <family name="roboto-flex">
+ <font supportedAxes="wght">RobotoFlex-Regular.ttf
<axis tag="wdth" stylevalue="100" />
</font>
</family>
@@ -109,11 +181,11 @@
</font>
<font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
</family>
- <family lang="und-Ethi" varFamilyType="1">
- <font postScriptName="NotoSansEthiopic-Regular">
+ <family lang="und-Ethi">
+ <font postScriptName="NotoSansEthiopic-Regular" supportedAxes="wght">
NotoSansEthiopic-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular" supportedAxes="wght">
NotoSerifEthiopic-VF.ttf
</font>
</family>
@@ -140,32 +212,32 @@
</font>
<font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
</family>
- <family lang="und-Armn" varFamilyType="1">
- <font postScriptName="NotoSansArmenian-Regular">
+ <family lang="und-Armn">
+ <font postScriptName="NotoSansArmenian-Regular" supportedAxes="wght">
NotoSansArmenian-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular" supportedAxes="wght">
NotoSerifArmenian-VF.ttf
</font>
</family>
- <family lang="und-Geor,und-Geok" varFamilyType="1">
- <font postScriptName="NotoSansGeorgian-Regular">
+ <family lang="und-Geor,und-Geok">
+ <font postScriptName="NotoSansGeorgian-Regular" supportedAxes="wght">
NotoSansGeorgian-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular" supportedAxes="wght">
NotoSerifGeorgian-VF.ttf
</font>
</family>
- <family lang="und-Deva" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansDevanagari-Regular">
+ <family lang="und-Deva" variant="elegant">
+ <font postScriptName="NotoSansDevanagari-Regular" supportedAxes="wght">
NotoSansDevanagari-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular" supportedAxes="wght">
NotoSerifDevanagari-VF.ttf
</font>
</family>
- <family lang="und-Deva" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansDevanagariUI-Regular">
+ <family lang="und-Deva" variant="compact">
+ <font postScriptName="NotoSansDevanagariUI-Regular" supportedAxes="wght">
NotoSansDevanagariUI-VF.ttf
</font>
</family>
@@ -178,21 +250,9 @@
NotoSansGujarati-Regular.ttf
</font>
<font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="700"/>
+ <font style="normal" fallbackFor="serif" postScriptName="NotoSerifGujarati-Regular"
+ supportedAxes="wght">
+ NotoSerifGujarati-VF.ttf
</font>
</family>
<family lang="und-Gujr" variant="compact">
@@ -201,81 +261,81 @@
</font>
<font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
</family>
- <family lang="und-Guru" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansGurmukhi-Regular">
+ <family lang="und-Guru" variant="elegant">
+ <font postScriptName="NotoSansGurmukhi-Regular" supportedAxes="wght">
NotoSansGurmukhi-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular" supportedAxes="wght">
NotoSerifGurmukhi-VF.ttf
</font>
</family>
- <family lang="und-Guru" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansGurmukhiUI-Regular">
+ <family lang="und-Guru" variant="compact">
+ <font postScriptName="NotoSansGurmukhiUI-Regular" supportedAxes="wght">
NotoSansGurmukhiUI-VF.ttf
</font>
</family>
- <family lang="und-Taml" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansTamil-Regular">
+ <family lang="und-Taml" variant="elegant">
+ <font postScriptName="NotoSansTamil-Regular" supportedAxes="wght">
NotoSansTamil-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular" supportedAxes="wght">
NotoSerifTamil-VF.ttf
</font>
</family>
- <family lang="und-Taml" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansTamilUI-Regular">
+ <family lang="und-Taml" variant="compact">
+ <font postScriptName="NotoSansTamilUI-Regular" supportedAxes="wght">
NotoSansTamilUI-VF.ttf
</font>
</family>
- <family lang="und-Mlym" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansMalayalam-Regular">
+ <family lang="und-Mlym" variant="elegant">
+ <font postScriptName="NotoSansMalayalam-Regular" supportedAxes="wght">
NotoSansMalayalam-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular" supportedAxes="wght">
NotoSerifMalayalam-VF.ttf
</font>
</family>
- <family lang="und-Mlym" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansMalayalamUI-Regular">
+ <family lang="und-Mlym" variant="compact">
+ <font postScriptName="NotoSansMalayalamUI-Regular" supportedAxes="wght">
NotoSansMalayalamUI-VF.ttf
</font>
</family>
- <family lang="und-Beng" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansBengali-Regular">
+ <family lang="und-Beng" variant="elegant">
+ <font postScriptName="NotoSansBengali-Regular" supportedAxes="wght">
NotoSansBengali-VF.ttf
</font>
<font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular">
NotoSerifBengali-VF.ttf
</font>
</family>
- <family lang="und-Beng" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansBengaliUI-Regular">
+ <family lang="und-Beng" variant="compact">
+ <font postScriptName="NotoSansBengaliUI-Regular" supportedAxes="wght">
NotoSansBengaliUI-VF.ttf
</font>
</family>
- <family lang="und-Telu" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansTelugu-Regular">
+ <family lang="und-Telu" variant="elegant">
+ <font postScriptName="NotoSansTelugu-Regular" supportedAxes="wght">
NotoSansTelugu-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular" supportedAxes="wght">
NotoSerifTelugu-VF.ttf
</font>
</family>
- <family lang="und-Telu" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansTeluguUI-Regular">
+ <family lang="und-Telu" variant="compact">
+ <font postScriptName="NotoSansTeluguUI-Regular" supportedAxes="wght">
NotoSansTeluguUI-VF.ttf
</font>
</family>
- <family lang="und-Knda" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansKannada-Regular">
+ <family lang="und-Knda" variant="elegant">
+ <font postScriptName="NotoSansKannada-Regular" supportedAxes="wght">
NotoSansKannada-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular" supportedAxes="wght">
NotoSerifKannada-VF.ttf
</font>
</family>
- <family lang="und-Knda" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansKannadaUI-Regular">
+ <family lang="und-Knda" variant="compact">
+ <font postScriptName="NotoSansKannadaUI-Regular" supportedAxes="wght">
NotoSansKannadaUI-VF.ttf
</font>
</family>
@@ -290,19 +350,20 @@
</font>
<font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
</family>
- <family lang="und-Sinh" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansSinhala-Regular">
+ <family lang="und-Sinh" variant="elegant">
+ <font postScriptName="NotoSansSinhala-Regular" supportedAxes="wght">
NotoSansSinhala-VF.ttf
</font>
<font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular">
NotoSerifSinhala-VF.ttf
</font>
</family>
- <family lang="und-Sinh" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansSinhalaUI-Regular">
+ <family lang="und-Sinh" variant="compact">
+ <font postScriptName="NotoSansSinhalaUI-Regular" supportedAxes="wght">
NotoSansSinhalaUI-VF.ttf
</font>
</family>
+ <!-- TODO: NotoSansKhmer uses non-standard wght value, so cannot use auto-adjustment. -->
<family lang="und-Khmr" variant="elegant">
<font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
NotoSansKhmer-VF.ttf
@@ -398,8 +459,8 @@
<family lang="und-Ahom">
<font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
</family>
- <family lang="und-Adlm" varFamilyType="1">
- <font postScriptName="NotoSansAdlam-Regular">
+ <family lang="und-Adlm">
+ <font postScriptName="NotoSansAdlam-Regular" supportedAxes="wght">
NotoSansAdlam-VF.ttf
</font>
</family>
@@ -686,8 +747,8 @@
NotoSansTaiViet-Regular.ttf
</font>
</family>
- <family lang="und-Tibt" varFamilyType="1">
- <font postScriptName="NotoSerifTibetan-Regular">
+ <family lang="und-Tibt">
+ <font postScriptName="NotoSerifTibetan-Regular" supportedAxes="wght">
NotoSerifTibetan-VF.ttf
</font>
</family>
@@ -865,27 +926,27 @@
<family lang="und-Dogr">
<font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
</family>
- <family lang="und-Medf" varFamilyType="1">
- <font postScriptName="NotoSansMedefaidrin-Regular">
+ <family lang="und-Medf">
+ <font postScriptName="NotoSansMedefaidrin-Regular" supportedAxes="wght">
NotoSansMedefaidrin-VF.ttf
</font>
</family>
- <family lang="und-Soyo" varFamilyType="1">
+ <family lang="und-Soyo" supportedAxes="wght">
<font postScriptName="NotoSansSoyombo-Regular">
NotoSansSoyombo-VF.ttf
</font>
</family>
- <family lang="und-Takr" varFamilyType="1">
+ <family lang="und-Takr" supportedAxes="wght">
<font postScriptName="NotoSansTakri-Regular">
NotoSansTakri-VF.ttf
</font>
</family>
- <family lang="und-Hmnp" varFamilyType="1">
+ <family lang="und-Hmnp" supportedAxes="wght">
<font postScriptName="NotoSerifHmongNyiakeng-Regular">
NotoSerifNyiakengPuachueHmong-VF.ttf
</font>
</family>
- <family lang="und-Yezi" varFamilyType="1">
+ <family lang="und-Yezi" supportedAxes="wght">
<font postScriptName="NotoSerifYezidi-Regular">
NotoSerifYezidi-VF.ttf
</font>
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
index 75bc74ebb553..c1ca67ef456a 100644
--- a/data/fonts/font_fallback_cjkvf.xml
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -11,12 +11,84 @@
effectively add 300 to the weight, this ensures that 900 is the bold
paired with the 500 weight, ensuring adequate contrast.
- TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+
+ The font_fallback.xml defines the list of font used by the system.
+
+ `familyset` node:
+ A `familyset` element must be a root node of the font_fallback.xml. No attributes are allowed
+ to `familyset` node.
+ The `familyset` node can contains `family` and `alias` nodes. Any other nodes will be ignored.
+
+ `family` node:
+ A `family` node defines a single font family definition.
+ A font family is a set of fonts for drawing text in various styles such as weight, slant.
+ There are three types of families, default family, named family and locale fallback family.
+
+ The default family is a special family node appeared the first node of the `familyset` node.
+ The default family is used as first priority fallback.
+ Only `name` attribute can be used for default family node. If the `name` attribute is
+ specified, This family will also works as named family.
+
+ The named family is a family that has name attribute. The named family defines a new fallback.
+ For example, if the name attribute is "serif", it creates serif fallback. Developers can
+ access the fallback by using Typeface#create API.
+ The named family can not have attribute other than `name` attribute. The `name` attribute
+ cannot be empty.
+
+ The locale fallback family is a font family that is used for fallback. The fallback family is
+ used when the named family or default family cannot be used. The locale fallback family can
+ have `lang` attribute and `variant` attribute. The `lang` attribute is an optional comma
+ separated BCP-47i language tag. The `variant` is an optional attribute that can be one one
+ `element`, `compact`. If a `variant` attribute is not specified, it is treated as default.
+
+ `alias` node:
+ An `alias` node defines a alias of named family with changing weight offset. An `alias` node
+ can have mandatory `name` and `to` attribute and optional `weight` attribute. This `alias`
+ defines new fallback that has the name of specified `name` attribute. The fallback list is
+ the same to the fallback that of the name specified with `to` attribute. If `weight` attribute
+ is specified, the base weight offset is shifted to the specified value. For example, if the
+ `weight` is 500, the output text is drawn with 500 of weight.
+
+ `font` node:
+ A `font` node defines a single font definition. There are two types of fonts, static font and
+ variable font.
+
+ A static font can have `weight`, `style`, `index` and `postScriptName` attributes. A `weight`
+ is a mandatory attribute that defines the weight of the font. Any number between 0 to 1000 is
+ valid. A `style` is a mandatory attribute that defines the style of the font. A 'style'
+ attribute can be `normal` or `italic`. An `index` is an optional attribute that defines the
+ index of the font collection. If this is not specified, it is treated as 0. If the font file
+ is not a font collection, this attribute is ignored. A `postScriptName` attribute is an
+ optional attribute. A PostScript name is used for identifying target of system font update.
+ If this is not specified, the system assumes the filename is same to PostScript name of the
+ font file. For example, if the font file is "Roboto-Regular.ttf", the system assume the
+ PostScript name of this font is "Roboto-Regular".
+
+ A variable font can be only defined for the variable font file. A variable font can have
+ `axis` child nodes for specifying axis values. A variable font can have all attribute of
+ static font and can have additional `supportedAxes` attribute. A `supportedAxes` attribute
+ is a comma separated supported axis tags. As of Android V, only `wght` and `ital` axes can
+ be specified.
+
+ If `supportedAxes` attribute is not specified, this `font` node works as static font of the
+ single instance of variable font specified with `axis` children.
+
+ If `supportedAxes` attribute is specified, the system dynamically create font instance for the
+ given weight and style value. If `wght` is specified in `supportedAxes` attribute the `weight`
+ attribute and `axis` child that has `wght` tag become optional and ignored because it is
+ determined by system at runtime. Similarly, if `ital` is specified in `supportedAxes`
+ attribute, the `style` attribute and `axis` child that has `ital` tag become optional and
+ ignored.
+
+ `axis` node:
+ An `axis` node defines a font variation value for a tag. An `axis` node can have two mandatory
+ attributes, `tag` and `value`. If the font is variable font and the same tag `axis` node is
+ specified in `supportedAxes` attribute, the style value works like a default instance.
-->
-<familyset version="23">
+<familyset>
<!-- first font is default -->
- <family name="sans-serif" varFamilyType="2">
- <font>Roboto-Regular.ttf
+ <family name="sans-serif">
+ <font supportedAxes="wght,ital">Roboto-Regular.ttf
<axis tag="wdth" stylevalue="100" />
</font>
</family>
@@ -32,8 +104,8 @@
<alias name="tahoma" to="sans-serif" />
<alias name="verdana" to="sans-serif" />
- <family name="sans-serif-condensed" varFamilyType="2">
- <font>Roboto-Regular.ttf
+ <family name="sans-serif-condensed">
+ <font supportedAxes="wght,ital">Roboto-Regular.ttf
<axis tag="wdth" stylevalue="75" />
</font>
</family>
@@ -72,8 +144,8 @@
<font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
</family>
- <family name="cursive" varFamilyType="1">
- <font>DancingScript-Regular.ttf</font>
+ <family name="cursive">
+ <font supportedAxes="wght">DancingScript-Regular.ttf</font>
</family>
<family name="sans-serif-smallcaps">
@@ -90,8 +162,8 @@
</family>
<alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
- <family name="roboto-flex" varFamilyType="2">
- <font>RobotoFlex-Regular.ttf
+ <family name="roboto-flex">
+ <font supportedAxes="wght">RobotoFlex-Regular.ttf
<axis tag="wdth" stylevalue="100" />
</font>
</family>
@@ -109,11 +181,11 @@
</font>
<font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
</family>
- <family lang="und-Ethi" varFamilyType="1">
- <font postScriptName="NotoSansEthiopic-Regular">
+ <family lang="und-Ethi">
+ <font postScriptName="NotoSansEthiopic-Regular" supportedAxes="wght">
NotoSansEthiopic-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular" supportedAxes="wght">
NotoSerifEthiopic-VF.ttf
</font>
</family>
@@ -140,32 +212,32 @@
</font>
<font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
</family>
- <family lang="und-Armn" varFamilyType="1">
- <font postScriptName="NotoSansArmenian-Regular">
+ <family lang="und-Armn">
+ <font postScriptName="NotoSansArmenian-Regular" supportedAxes="wght">
NotoSansArmenian-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular" supportedAxes="wght">
NotoSerifArmenian-VF.ttf
</font>
</family>
- <family lang="und-Geor,und-Geok" varFamilyType="1">
- <font postScriptName="NotoSansGeorgian-Regular">
+ <family lang="und-Geor,und-Geok">
+ <font postScriptName="NotoSansGeorgian-Regular" supportedAxes="wght">
NotoSansGeorgian-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular" supportedAxes="wght">
NotoSerifGeorgian-VF.ttf
</font>
</family>
- <family lang="und-Deva" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansDevanagari-Regular">
+ <family lang="und-Deva" variant="elegant">
+ <font postScriptName="NotoSansDevanagari-Regular" supportedAxes="wght">
NotoSansDevanagari-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular" supportedAxes="wght">
NotoSerifDevanagari-VF.ttf
</font>
</family>
- <family lang="und-Deva" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansDevanagariUI-Regular">
+ <family lang="und-Deva" variant="compact">
+ <font postScriptName="NotoSansDevanagariUI-Regular" supportedAxes="wght">
NotoSansDevanagariUI-VF.ttf
</font>
</family>
@@ -178,21 +250,9 @@
NotoSansGujarati-Regular.ttf
</font>
<font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" fallbackFor="serif"
- postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
- <axis tag="wght" stylevalue="700"/>
+ <font style="normal" fallbackFor="serif" postScriptName="NotoSerifGujarati-Regular"
+ supportedAxes="wght">
+ NotoSerifGujarati-VF.ttf
</font>
</family>
<family lang="und-Gujr" variant="compact">
@@ -201,81 +261,81 @@
</font>
<font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
</family>
- <family lang="und-Guru" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansGurmukhi-Regular">
+ <family lang="und-Guru" variant="elegant">
+ <font postScriptName="NotoSansGurmukhi-Regular" supportedAxes="wght">
NotoSansGurmukhi-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular" supportedAxes="wght">
NotoSerifGurmukhi-VF.ttf
</font>
</family>
- <family lang="und-Guru" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansGurmukhiUI-Regular">
+ <family lang="und-Guru" variant="compact">
+ <font postScriptName="NotoSansGurmukhiUI-Regular" supportedAxes="wght">
NotoSansGurmukhiUI-VF.ttf
</font>
</family>
- <family lang="und-Taml" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansTamil-Regular">
+ <family lang="und-Taml" variant="elegant">
+ <font postScriptName="NotoSansTamil-Regular" supportedAxes="wght">
NotoSansTamil-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular" supportedAxes="wght">
NotoSerifTamil-VF.ttf
</font>
</family>
- <family lang="und-Taml" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansTamilUI-Regular">
+ <family lang="und-Taml" variant="compact">
+ <font postScriptName="NotoSansTamilUI-Regular" supportedAxes="wght">
NotoSansTamilUI-VF.ttf
</font>
</family>
- <family lang="und-Mlym" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansMalayalam-Regular">
+ <family lang="und-Mlym" variant="elegant">
+ <font postScriptName="NotoSansMalayalam-Regular" supportedAxes="wght">
NotoSansMalayalam-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular" supportedAxes="wght">
NotoSerifMalayalam-VF.ttf
</font>
</family>
- <family lang="und-Mlym" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansMalayalamUI-Regular">
+ <family lang="und-Mlym" variant="compact">
+ <font postScriptName="NotoSansMalayalamUI-Regular" supportedAxes="wght">
NotoSansMalayalamUI-VF.ttf
</font>
</family>
- <family lang="und-Beng" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansBengali-Regular">
+ <family lang="und-Beng" variant="elegant">
+ <font postScriptName="NotoSansBengali-Regular" supportedAxes="wght">
NotoSansBengali-VF.ttf
</font>
<font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular">
NotoSerifBengali-VF.ttf
</font>
</family>
- <family lang="und-Beng" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansBengaliUI-Regular">
+ <family lang="und-Beng" variant="compact">
+ <font postScriptName="NotoSansBengaliUI-Regular" supportedAxes="wght">
NotoSansBengaliUI-VF.ttf
</font>
</family>
- <family lang="und-Telu" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansTelugu-Regular">
+ <family lang="und-Telu" variant="elegant">
+ <font postScriptName="NotoSansTelugu-Regular" supportedAxes="wght">
NotoSansTelugu-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular" supportedAxes="wght">
NotoSerifTelugu-VF.ttf
</font>
</family>
- <family lang="und-Telu" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansTeluguUI-Regular">
+ <family lang="und-Telu" variant="compact">
+ <font postScriptName="NotoSansTeluguUI-Regular" supportedAxes="wght">
NotoSansTeluguUI-VF.ttf
</font>
</family>
- <family lang="und-Knda" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansKannada-Regular">
+ <family lang="und-Knda" variant="elegant">
+ <font postScriptName="NotoSansKannada-Regular" supportedAxes="wght">
NotoSansKannada-VF.ttf
</font>
- <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular">
+ <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular" supportedAxes="wght">
NotoSerifKannada-VF.ttf
</font>
</family>
- <family lang="und-Knda" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansKannadaUI-Regular">
+ <family lang="und-Knda" variant="compact">
+ <font postScriptName="NotoSansKannadaUI-Regular" supportedAxes="wght">
NotoSansKannadaUI-VF.ttf
</font>
</family>
@@ -290,19 +350,20 @@
</font>
<font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
</family>
- <family lang="und-Sinh" variant="elegant" varFamilyType="1">
- <font postScriptName="NotoSansSinhala-Regular">
+ <family lang="und-Sinh" variant="elegant">
+ <font postScriptName="NotoSansSinhala-Regular" supportedAxes="wght">
NotoSansSinhala-VF.ttf
</font>
<font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular">
NotoSerifSinhala-VF.ttf
</font>
</family>
- <family lang="und-Sinh" variant="compact" varFamilyType="1">
- <font postScriptName="NotoSansSinhalaUI-Regular">
+ <family lang="und-Sinh" variant="compact">
+ <font postScriptName="NotoSansSinhalaUI-Regular" supportedAxes="wght">
NotoSansSinhalaUI-VF.ttf
</font>
</family>
+ <!-- TODO: NotoSansKhmer uses non-standard wght value, so cannot use auto-adjustment. -->
<family lang="und-Khmr" variant="elegant">
<font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
NotoSansKhmer-VF.ttf
@@ -398,8 +459,8 @@
<family lang="und-Ahom">
<font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
</family>
- <family lang="und-Adlm" varFamilyType="1">
- <font postScriptName="NotoSansAdlam-Regular">
+ <family lang="und-Adlm">
+ <font postScriptName="NotoSansAdlam-Regular" supportedAxes="wght">
NotoSansAdlam-VF.ttf
</font>
</family>
@@ -686,8 +747,8 @@
NotoSansTaiViet-Regular.ttf
</font>
</family>
- <family lang="und-Tibt" varFamilyType="1">
- <font postScriptName="NotoSerifTibetan-Regular">
+ <family lang="und-Tibt">
+ <font postScriptName="NotoSerifTibetan-Regular" supportedAxes="wght">
NotoSerifTibetan-VF.ttf
</font>
</family>
@@ -707,123 +768,36 @@
<font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
</family>
<family lang="zh-Hans">
- <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"
+ supportedAxes="wght">
NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
+ <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
+ for making regular style as default. -->
+ <axis tag="wght" stylevalue="400" />
</font>
<font weight="400" style="normal" index="2" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
</font>
</family>
<family lang="zh-Hant,zh-Bopo">
- <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"
+ supportedAxes="wght">
NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
+ <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
+ for making regular style as default. -->
+ <axis tag="wght" stylevalue="400" />
</font>
<font weight="400" style="normal" index="3" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
</font>
</family>
<family lang="ja">
- <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"
+ supportedAxes="wght">
NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
+ <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
+ for making regular style as default. -->
+ <axis tag="wght" stylevalue="400" />
</font>
<font weight="400" style="normal" index="0" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
@@ -840,41 +814,12 @@
</font>
</family>
<family lang="ko">
- <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="100"/>
- </font>
- <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="200"/>
- </font>
- <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="300"/>
- </font>
- <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="500"/>
- </font>
- <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="600"/>
- </font>
- <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="700"/>
- </font>
- <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
- NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="800"/>
- </font>
- <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"
+ supportedAxes="wght">
NotoSansCJK-Regular.ttc
- <axis tag="wght" stylevalue="900"/>
+ <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
+ for making regular style as default. -->
+ <axis tag="wght" stylevalue="400" />
</font>
<font weight="400" style="normal" index="1" fallbackFor="serif"
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
@@ -997,27 +942,27 @@
<family lang="und-Dogr">
<font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
</family>
- <family lang="und-Medf" varFamilyType="1">
- <font postScriptName="NotoSansMedefaidrin-Regular">
+ <family lang="und-Medf">
+ <font postScriptName="NotoSansMedefaidrin-Regular" supportedAxes="wght">
NotoSansMedefaidrin-VF.ttf
</font>
</family>
- <family lang="und-Soyo" varFamilyType="1">
+ <family lang="und-Soyo" supportedAxes="wght">
<font postScriptName="NotoSansSoyombo-Regular">
NotoSansSoyombo-VF.ttf
</font>
</family>
- <family lang="und-Takr" varFamilyType="1">
+ <family lang="und-Takr" supportedAxes="wght">
<font postScriptName="NotoSansTakri-Regular">
NotoSansTakri-VF.ttf
</font>
</family>
- <family lang="und-Hmnp" varFamilyType="1">
+ <family lang="und-Hmnp" supportedAxes="wght">
<font postScriptName="NotoSerifHmongNyiakeng-Regular">
NotoSerifNyiakengPuachueHmong-VF.ttf
</font>
</family>
- <family lang="und-Yezi" varFamilyType="1">
+ <family lang="und-Yezi" supportedAxes="wght">
<font postScriptName="NotoSerifYezidi-Regular">
NotoSerifYezidi-VF.ttf
</font>
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index d1aceafc4e2c..b33a5d2dcef8 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -18,6 +18,7 @@ package android.graphics;
import android.annotation.ColorInt;
import android.annotation.ColorLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -30,6 +31,8 @@ import android.graphics.text.TextRunShaper;
import android.os.Build;
import android.text.TextShaper;
+import com.android.graphics.hwui.flags.Flags;
+
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -766,6 +769,21 @@ public class Canvas extends BaseCanvas {
}
/**
+ * Preconcat the current matrix with the specified matrix. If the specified
+ * matrix is null, this method does nothing. If the canvas's matrix is changed in the z-axis
+ * through this function, the deprecated {@link #getMatrix()} method will return a 3x3 with
+ * z-axis info stripped away.
+ *
+ * @param m The 4x4 matrix to preconcatenate with the current matrix
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void concat44(@Nullable Matrix44 m) {
+ if (m != null) {
+ nConcat(mNativeCanvasWrapper, m.mBackingArray);
+ }
+ }
+
+ /**
* Completely replace the current matrix with the specified matrix. If the
* matrix parameter is null, then the current matrix is reset to identity.
*
@@ -1444,6 +1462,8 @@ public class Canvas extends BaseCanvas {
private static native void nSkew(long canvasHandle, float sx, float sy);
@CriticalNative
private static native void nConcat(long nativeCanvas, long nativeMatrix);
+ @FastNative
+ private static native void nConcat(long nativeCanvas, float[] mat);
@CriticalNative
private static native void nSetMatrix(long nativeCanvas, long nativeMatrix);
@CriticalNative
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 52b0b95d3e76..17c2dd94cd36 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,10 +16,6 @@
package android.graphics;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
import static android.text.FontConfig.NamedFamilyList;
import android.annotation.NonNull;
@@ -32,7 +28,6 @@ import android.os.Build;
import android.os.LocaleList;
import android.text.FontConfig;
import android.util.ArraySet;
-import android.util.Log;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@@ -65,6 +60,7 @@ public class FontListParser {
private static final String VARIANT_ELEGANT = "elegant";
// XML constants for Font.
+ public static final String ATTR_SUPPORTED_AXES = "supportedAxes";
public static final String ATTR_INDEX = "index";
public static final String ATTR_WEIGHT = "weight";
public static final String ATTR_POSTSCRIPT_NAME = "postScriptName";
@@ -78,6 +74,10 @@ public class FontListParser {
public static final String ATTR_TAG = "tag";
public static final String ATTR_STYLEVALUE = "stylevalue";
+ // The tag string for variable font type resolution.
+ private static final String TAG_WGHT = "wght";
+ private static final String TAG_ITAL = "ital";
+
/* Parse fallback list (no names) */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException {
@@ -263,7 +263,6 @@ public class FontListParser {
final String lang = parser.getAttributeValue("", "lang");
final String variant = parser.getAttributeValue(null, "variant");
final String ignore = parser.getAttributeValue(null, "ignore");
- final String varFamilyTypeStr = parser.getAttributeValue(null, "varFamilyType");
final List<FontConfig.Font> fonts = new ArrayList<>();
while (keepReading(parser)) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
@@ -286,45 +285,12 @@ public class FontListParser {
intVariant = FontConfig.FontFamily.VARIANT_ELEGANT;
}
}
- int varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- if (varFamilyTypeStr != null) {
- varFamilyType = Integer.parseInt(varFamilyTypeStr);
- if (varFamilyType <= -1 || varFamilyType > 3) {
- Log.e(TAG, "Error: unexpected varFamilyType value: " + varFamilyTypeStr);
- varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- }
-
- // validation but don't read font content for performance reasons.
- switch (varFamilyType) {
- case VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY:
- if (fonts.size() != 1) {
- Log.e(TAG, "Error: Single font support wght axis, but two or more fonts are"
- + " included in the font family.");
- varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- }
- break;
- case VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL:
- if (fonts.size() != 1) {
- Log.e(TAG, "Error: Single font support both ital and wght axes, but two or"
- + " more fonts are included in the font family.");
- varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- }
- break;
- case VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT:
- if (fonts.size() != 2) {
- Log.e(TAG, "Error: two fonts that support wght axis, but one or three or"
- + " more fonts are included in the font family.");
- varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
- }
- }
- }
boolean skip = (ignore != null && (ignore.equals("true") || ignore.equals("1")));
if (skip || fonts.isEmpty()) {
return null;
}
- return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant,
- varFamilyType);
+ return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant);
}
private static void throwIfAttributeExists(String attrName, XmlPullParser parser) {
@@ -407,6 +373,7 @@ public class FontListParser {
boolean isItalic = STYLE_ITALIC.equals(parser.getAttributeValue(null, ATTR_STYLE));
String fallbackFor = parser.getAttributeValue(null, ATTR_FALLBACK_FOR);
String postScriptName = parser.getAttributeValue(null, ATTR_POSTSCRIPT_NAME);
+ final String supportedAxes = parser.getAttributeValue(null, ATTR_SUPPORTED_AXES);
StringBuilder filename = new StringBuilder();
while (keepReading(parser)) {
if (parser.getEventType() == XmlPullParser.TEXT) {
@@ -422,6 +389,18 @@ public class FontListParser {
}
String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
+ int varTypeAxes = 0;
+ if (supportedAxes != null) {
+ for (String tag : supportedAxes.split(",")) {
+ String strippedTag = tag.strip();
+ if (strippedTag.equals(TAG_WGHT)) {
+ varTypeAxes |= FontConfig.Font.VAR_TYPE_AXES_WGHT;
+ } else if (strippedTag.equals(TAG_ITAL)) {
+ varTypeAxes |= FontConfig.Font.VAR_TYPE_AXES_ITAL;
+ }
+ }
+ }
+
if (postScriptName == null) {
// If post script name was not provided, assume the file name is same to PostScript
// name.
@@ -462,7 +441,8 @@ public class FontListParser {
),
index,
varSettings,
- fallbackFor);
+ fallbackFor,
+ varTypeAxes);
}
private static String findUpdatedFontFile(String psName,
diff --git a/graphics/java/android/graphics/Matrix44.java b/graphics/java/android/graphics/Matrix44.java
new file mode 100644
index 000000000000..7cc0eb7a6728
--- /dev/null
+++ b/graphics/java/android/graphics/Matrix44.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright 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 android.graphics;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.graphics.hwui.flags.Flags;
+
+import java.util.Arrays;
+
+/**
+ * The Matrix44 class holds a 4x4 matrix for transforming coordinates. It is similar to
+ * {@link Matrix}, and should be used when you want to manipulate the canvas in 3D. Values are kept
+ * in row-major order. The values and operations are treated as column vectors.
+ */
+@FlaggedApi(Flags.FLAG_MATRIX_44)
+public class Matrix44 {
+ final float[] mBackingArray;
+ /**
+ * The default Matrix44 constructor will instantiate an identity matrix.
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public Matrix44() {
+ mBackingArray = new float[]{1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f};
+ }
+
+ /**
+ * Creates and returns a Matrix44 by taking the 3x3 Matrix and placing it on the 0 of the z-axis
+ * by setting row {@code 2} and column {@code 2} to the identity as seen in the following
+ * operation:
+ * <pre class="prettyprint">
+ * [ a b c ] [ a b 0 c ]
+ * [ d e f ] -> [ d e 0 f ]
+ * [ g h i ] [ 0 0 1 0 ]
+ * [ g h 0 i ]
+ * </pre>
+ *
+ * @param mat A 3x3 Matrix to be converted (original Matrix will not be changed)
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public Matrix44(@NonNull Matrix mat) {
+ float[] m = new float[9];
+ mat.getValues(m);
+ mBackingArray = new float[]{m[0], m[1], 0.0f, m[2],
+ m[3], m[4], 0.0f, m[5],
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ m[6], m[7], 0.0f, m[8]};
+ }
+
+ /**
+ * Copies matrix values into the provided array in row-major order.
+ *
+ * @param dst The float array where values will be copied, must be of length 16
+ * @throws IllegalArgumentException if the destination float array is not of length 16
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void getValues(@NonNull float [] dst) {
+ if (dst.length == 16) {
+ System.arraycopy(mBackingArray, 0, dst, 0, mBackingArray.length);
+ } else {
+ throw new IllegalArgumentException("Dst array must be of length 16");
+ }
+ }
+
+ /**
+ * Replaces the Matrix's values with the values in the provided array.
+ *
+ * @param src A float array of length 16. Floats are treated in row-major order
+ * @throws IllegalArgumentException if the destination float array is not of length 16
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void setValues(@NonNull float[] src) {
+ if (src.length == 16) {
+ System.arraycopy(src, 0, mBackingArray, 0, mBackingArray.length);
+ } else {
+ throw new IllegalArgumentException("Src array must be of length 16");
+ }
+ }
+
+ /**
+ * Gets the value at the matrix's row and column.
+ *
+ * @param row An integer from 0 to 4 indicating the row of the value to get
+ * @param col An integer from 0 to 4 indicating the column of the value to get
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public float get(int row, int col) {
+ if (row >= 0 && row < 4 && col >= 0 && col < 4) {
+ return mBackingArray[row * 4 + col];
+ }
+ throw new IllegalArgumentException("invalid row and column values");
+ }
+
+ /**
+ * Sets the value at the matrix's row and column to the provided value.
+ *
+ * @param row An integer from 0 to 4 indicating the row of the value to change
+ * @param col An integer from 0 to 4 indicating the column of the value to change
+ * @param val The value the element at the specified index will be set to
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void set(int row, int col, float val) {
+ if (row >= 0 && row < 4 && col >= 0 && col < 4) {
+ mBackingArray[row * 4 + col] = val;
+ } else {
+ throw new IllegalArgumentException("invalid row and column values");
+ }
+ }
+
+ /**
+ * Sets the Matrix44 to the identity matrix.
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void reset() {
+ for (int i = 0; i < mBackingArray.length; i++) {
+ mBackingArray[i] = (i % 4 == i / 4) ? 1.0f : 0.0f;
+ }
+ }
+
+ /**
+ * Inverts the Matrix44, then return true if successful, false if unable to invert.
+ *
+ * @return {@code true} on success, {@code false} otherwise
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public boolean invert() {
+ float a00 = mBackingArray[0];
+ float a01 = mBackingArray[1];
+ float a02 = mBackingArray[2];
+ float a03 = mBackingArray[3];
+ float a10 = mBackingArray[4];
+ float a11 = mBackingArray[5];
+ float a12 = mBackingArray[6];
+ float a13 = mBackingArray[7];
+ float a20 = mBackingArray[8];
+ float a21 = mBackingArray[9];
+ float a22 = mBackingArray[10];
+ float a23 = mBackingArray[11];
+ float a30 = mBackingArray[12];
+ float a31 = mBackingArray[13];
+ float a32 = mBackingArray[14];
+ float a33 = mBackingArray[15];
+ float b00 = a00 * a11 - a01 * a10;
+ float b01 = a00 * a12 - a02 * a10;
+ float b02 = a00 * a13 - a03 * a10;
+ float b03 = a01 * a12 - a02 * a11;
+ float b04 = a01 * a13 - a03 * a11;
+ float b05 = a02 * a13 - a03 * a12;
+ float b06 = a20 * a31 - a21 * a30;
+ float b07 = a20 * a32 - a22 * a30;
+ float b08 = a20 * a33 - a23 * a30;
+ float b09 = a21 * a32 - a22 * a31;
+ float b10 = a21 * a33 - a23 * a31;
+ float b11 = a22 * a33 - a23 * a32;
+ float det = (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06);
+ if (det == 0.0f) {
+ return false;
+ }
+ float invDet = 1.0f / det;
+ mBackingArray[0] = ((a11 * b11 - a12 * b10 + a13 * b09) * invDet);
+ mBackingArray[1] = ((-a01 * b11 + a02 * b10 - a03 * b09) * invDet);
+ mBackingArray[2] = ((a31 * b05 - a32 * b04 + a33 * b03) * invDet);
+ mBackingArray[3] = ((-a21 * b05 + a22 * b04 - a23 * b03) * invDet);
+ mBackingArray[4] = ((-a10 * b11 + a12 * b08 - a13 * b07) * invDet);
+ mBackingArray[5] = ((a00 * b11 - a02 * b08 + a03 * b07) * invDet);
+ mBackingArray[6] = ((-a30 * b05 + a32 * b02 - a33 * b01) * invDet);
+ mBackingArray[7] = ((a20 * b05 - a22 * b02 + a23 * b01) * invDet);
+ mBackingArray[8] = ((a10 * b10 - a11 * b08 + a13 * b06) * invDet);
+ mBackingArray[9] = ((-a00 * b10 + a01 * b08 - a03 * b06) * invDet);
+ mBackingArray[10] = ((a30 * b04 - a31 * b02 + a33 * b00) * invDet);
+ mBackingArray[11] = ((-a20 * b04 + a21 * b02 - a23 * b00) * invDet);
+ mBackingArray[12] = ((-a10 * b09 + a11 * b07 - a12 * b06) * invDet);
+ mBackingArray[13] = ((a00 * b09 - a01 * b07 + a02 * b06) * invDet);
+ mBackingArray[14] = ((-a30 * b03 + a31 * b01 - a32 * b00) * invDet);
+ mBackingArray[15] = ((a20 * b03 - a21 * b01 + a22 * b00) * invDet);
+ return true;
+ }
+
+ /**
+ * Returns true if Matrix44 is equal to identity matrix.
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public boolean isIdentity() {
+ for (int i = 0; i < mBackingArray.length; i++) {
+ float expected = (i % 4 == i / 4) ? 1.0f : 0.0f;
+ if (expected != mBackingArray[i]) return false;
+ }
+ return true;
+ }
+
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ private static float dot(Matrix44 a, Matrix44 b, int row, int col) {
+ return (a.get(row, 0) * b.get(0, col))
+ + (a.get(row, 1) * b.get(1, col))
+ + (a.get(row, 2) * b.get(2, col))
+ + (a.get(row, 3) * b.get(3, col));
+ }
+
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ private static float dot(float r0, float r1, float r2, float r3,
+ float c0, float c1, float c2, float c3) {
+ return (r0 * c0) + (r1 * c1) + (r2 * c2) + (r3 * c3);
+ }
+
+ /**
+ * Multiplies (x, y, z, w) vector by the Matrix44, then returns the new (x, y, z, w). Users
+ * should set {@code w} to 1 to indicate the coordinates are normalized.
+ *
+ * @return An array of length 4 that represents the x, y, z, w (where w is perspective) value
+ * after multiplying x, y, z, 1 by the matrix
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull float[] map(float x, float y, float z, float w) {
+ float[] dst = new float[4];
+ this.map(x, y, z, w, dst);
+ return dst;
+ }
+
+ /**
+ * Multiplies (x, y, z, w) vector by the Matrix44, then returns the new (x, y, z, w). Users
+ * should set {@code w} to 1 to indicate the coordinates are normalized.
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public void map(float x, float y, float z, float w, @NonNull float[] dst) {
+ if (dst.length != 4) {
+ throw new IllegalArgumentException("Dst array must be of length 4");
+ }
+ dst[0] = x * mBackingArray[0] + y * mBackingArray[1]
+ + z * mBackingArray[2] + w * mBackingArray[3];
+ dst[1] = x * mBackingArray[4] + y * mBackingArray[5]
+ + z * mBackingArray[6] + w * mBackingArray[7];
+ dst[2] = x * mBackingArray[8] + y * mBackingArray[9]
+ + z * mBackingArray[10] + w * mBackingArray[11];
+ dst[3] = x * mBackingArray[12] + y * mBackingArray[13]
+ + z * mBackingArray[14] + w * mBackingArray[15];
+ }
+
+ /**
+ * Multiplies `this` matrix (A) and provided Matrix (B) in the order of A * B.
+ * The result is saved in `this` Matrix.
+ *
+ * @param b The second Matrix in the concatenation operation
+ * @return A reference to this Matrix, which can be used to chain Matrix operations
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull Matrix44 concat(@NonNull Matrix44 b) {
+ float val00 = dot(this, b, 0, 0);
+ float val01 = dot(this, b, 0, 1);
+ float val02 = dot(this, b, 0, 2);
+ float val03 = dot(this, b, 0, 3);
+ float val10 = dot(this, b, 1, 0);
+ float val11 = dot(this, b, 1, 1);
+ float val12 = dot(this, b, 1, 2);
+ float val13 = dot(this, b, 1, 3);
+ float val20 = dot(this, b, 2, 0);
+ float val21 = dot(this, b, 2, 1);
+ float val22 = dot(this, b, 2, 2);
+ float val23 = dot(this, b, 2, 3);
+ float val30 = dot(this, b, 3, 0);
+ float val31 = dot(this, b, 3, 1);
+ float val32 = dot(this, b, 3, 2);
+ float val33 = dot(this, b, 3, 3);
+
+ mBackingArray[0] = val00;
+ mBackingArray[1] = val01;
+ mBackingArray[2] = val02;
+ mBackingArray[3] = val03;
+ mBackingArray[4] = val10;
+ mBackingArray[5] = val11;
+ mBackingArray[6] = val12;
+ mBackingArray[7] = val13;
+ mBackingArray[8] = val20;
+ mBackingArray[9] = val21;
+ mBackingArray[10] = val22;
+ mBackingArray[11] = val23;
+ mBackingArray[12] = val30;
+ mBackingArray[13] = val31;
+ mBackingArray[14] = val32;
+ mBackingArray[15] = val33;
+
+ return this;
+ }
+
+ /**
+ * Applies a rotation around a given axis, then returns self.
+ * {@code x}, {@code y}, {@code z} represent the axis by which to rotate around.
+ * For example, pass in {@code 1, 0, 0} to rotate around the x-axis.
+ * The axis provided will be normalized.
+ *
+ * @param deg Amount in degrees to rotate the matrix about the x-axis
+ * @param xComp X component of the rotation axis
+ * @param yComp Y component of the rotation axis
+ * @param zComp Z component of the rotation axis
+ * @return A reference to this Matrix, which can be used to chain Matrix operations
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull Matrix44 rotate(float deg, float xComp, float yComp, float zComp) {
+ float sum = xComp + yComp + zComp;
+ float x = xComp / sum;
+ float y = yComp / sum;
+ float z = zComp / sum;
+
+ float c = (float) Math.cos(deg * Math.PI / 180.0f);
+ float s = (float) Math.sin(deg * Math.PI / 180.0f);
+ float t = 1 - c;
+
+ float rotVals00 = t * x * x + c;
+ float rotVals01 = t * x * y - s * z;
+ float rotVals02 = t * x * z + s * y;
+ float rotVals10 = t * x * y + s * z;
+ float rotVals11 = t * y * y + c;
+ float rotVals12 = t * y * z - s * x;
+ float rotVals20 = t * x * z - s * y;
+ float rotVals21 = t * y * z + s * x;
+ float rotVals22 = t * z * z + c;
+
+ float v00 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ rotVals00, rotVals10, rotVals20, 0);
+ float v01 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ rotVals01, rotVals11, rotVals21, 0);
+ float v02 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ rotVals02, rotVals12, rotVals22, 0);
+ float v03 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ 0, 0, 0, 1);
+ float v10 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ rotVals00, rotVals10, rotVals20, 0);
+ float v11 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ rotVals01, rotVals11, rotVals21, 0);
+ float v12 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ rotVals02, rotVals12, rotVals22, 0);
+ float v13 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ 0, 0, 0, 1);
+ float v20 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ rotVals00, rotVals10, rotVals20, 0);
+ float v21 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ rotVals01, rotVals11, rotVals21, 0);
+ float v22 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ rotVals02, rotVals12, rotVals22, 0);
+ float v23 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ 0, 0, 0, 1);
+ float v30 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+ rotVals00, rotVals10, rotVals20, 0);
+ float v31 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+ rotVals01, rotVals11, rotVals21, 0);
+ float v32 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+ rotVals02, rotVals12, rotVals22, 0);
+ float v33 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+ 0, 0, 0, 1);
+
+ mBackingArray[0] = v00;
+ mBackingArray[1] = v01;
+ mBackingArray[2] = v02;
+ mBackingArray[3] = v03;
+ mBackingArray[4] = v10;
+ mBackingArray[5] = v11;
+ mBackingArray[6] = v12;
+ mBackingArray[7] = v13;
+ mBackingArray[8] = v20;
+ mBackingArray[9] = v21;
+ mBackingArray[10] = v22;
+ mBackingArray[11] = v23;
+ mBackingArray[12] = v30;
+ mBackingArray[13] = v31;
+ mBackingArray[14] = v32;
+ mBackingArray[15] = v33;
+
+ return this;
+ }
+
+ /**
+ * Applies scaling factors to `this` Matrix44, then returns self. Pass 1s for no change.
+ *
+ * @param x Scaling factor for the x-axis
+ * @param y Scaling factor for the y-axis
+ * @param z Scaling factor for the z-axis
+ * @return A reference to this Matrix, which can be used to chain Matrix operations
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull Matrix44 scale(float x, float y, float z) {
+ mBackingArray[0] *= x;
+ mBackingArray[4] *= x;
+ mBackingArray[8] *= x;
+ mBackingArray[12] *= x;
+ mBackingArray[1] *= y;
+ mBackingArray[5] *= y;
+ mBackingArray[9] *= y;
+ mBackingArray[13] *= y;
+ mBackingArray[2] *= z;
+ mBackingArray[6] *= z;
+ mBackingArray[10] *= z;
+ mBackingArray[14] *= z;
+
+ return this;
+ }
+
+ /**
+ * Applies a translation to `this` Matrix44, then returns self.
+ *
+ * @param x Translation for the x-axis
+ * @param y Translation for the y-axis
+ * @param z Translation for the z-axis
+ * @return A reference to this Matrix, which can be used to chain Matrix operations
+ */
+ @FlaggedApi(Flags.FLAG_MATRIX_44)
+ public @NonNull Matrix44 translate(float x, float y, float z) {
+ float newX = x * mBackingArray[0] + y * mBackingArray[1]
+ + z * mBackingArray[2] + mBackingArray[3];
+ float newY = x * mBackingArray[4] + y * mBackingArray[5]
+ + z * mBackingArray[6] + mBackingArray[7];
+ float newZ = x * mBackingArray[8] + y * mBackingArray[9]
+ + z * mBackingArray[10] + mBackingArray[11];
+ float newW = x * mBackingArray[12] + y * mBackingArray[13]
+ + z * mBackingArray[14] + mBackingArray[15];
+
+ mBackingArray[3] = newX;
+ mBackingArray[7] = newY;
+ mBackingArray[11] = newZ;
+ mBackingArray[15] = newW;
+
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("""
+ | %f %f %f %f |
+ | %f %f %f %f |
+ | %f %f %f %f |
+ | %f %f %f %f |
+ """, mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+ mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+ mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+ mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15]);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Matrix44) {
+ return Arrays.equals(mBackingArray, ((Matrix44) obj).mBackingArray);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) mBackingArray[0] + (int) mBackingArray[1] + (int) mBackingArray[2]
+ + (int) mBackingArray[3] + (int) mBackingArray[4] + (int) mBackingArray[5]
+ + (int) mBackingArray[6] + (int) mBackingArray[7] + (int) mBackingArray[8]
+ + (int) mBackingArray[9] + (int) mBackingArray[10] + (int) mBackingArray[11]
+ + (int) mBackingArray[12] + (int) mBackingArray[13] + (int) mBackingArray[14]
+ + (int) mBackingArray[15];
+ }
+
+}
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 3ef714ed8bdf..a90961eb6d2e 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -100,6 +100,71 @@ public final class SystemFonts {
}
}
+ /** @hide */
+ @VisibleForTesting
+ public static @FontFamily.Builder.VariableFontFamilyType int resolveVarFamilyType(
+ @NonNull FontConfig.FontFamily xmlFamily,
+ @Nullable String familyName) {
+ int wghtCount = 0;
+ int italCount = 0;
+ int targetFonts = 0;
+ boolean hasItalicFont = false;
+
+ List<FontConfig.Font> fonts = xmlFamily.getFontList();
+ for (int i = 0; i < fonts.size(); ++i) {
+ FontConfig.Font font = fonts.get(i);
+
+ if (familyName == null) { // for default family
+ if (font.getFontFamilyName() != null) {
+ continue; // this font is not for the default family.
+ }
+ } else { // for the specific family
+ if (!familyName.equals(font.getFontFamilyName())) {
+ continue; // this font is not for given family.
+ }
+ }
+
+ final int varTypeAxes = font.getVarTypeAxes();
+ if (varTypeAxes == 0) {
+ // If we see static font, we can immediately return as VAR_TYPE_NONE.
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
+ }
+
+ if ((varTypeAxes & FontConfig.Font.VAR_TYPE_AXES_WGHT) != 0) {
+ wghtCount++;
+ }
+
+ if ((varTypeAxes & FontConfig.Font.VAR_TYPE_AXES_ITAL) != 0) {
+ italCount++;
+ }
+
+ if (font.getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC) {
+ hasItalicFont = true;
+ }
+ targetFonts++;
+ }
+
+ if (italCount == 0) { // No ital font.
+ if (targetFonts == 1 && wghtCount == 1) {
+ // If there is only single font that has wght, use it for regular style and
+ // use synthetic bolding for italic.
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
+ } else if (targetFonts == 2 && wghtCount == 2 && hasItalicFont) {
+ // If there are two fonts and italic font is available, use them for regular and
+ // italic separately. (It is impossible to have two italic fonts. It will end up
+ // with Typeface creation failure.)
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
+ }
+ } else if (italCount == 1) {
+ // If ital font is included, a single font should support both wght and ital.
+ if (wghtCount == 1 && targetFonts == 1) {
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
+ }
+ }
+ // Otherwise, unsupported.
+ return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
+ }
+
private static void pushFamilyToFallback(@NonNull FontConfig.FontFamily xmlFamily,
@NonNull ArrayMap<String, NativeFamilyListSet> fallbackMap,
@NonNull Map<String, ByteBuffer> cache) {
@@ -126,7 +191,7 @@ public final class SystemFonts {
}
final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
- defaultFonts, languageTags, variant, xmlFamily.getVariableFontFamilyType(), false,
+ defaultFonts, languageTags, variant, resolveVarFamilyType(xmlFamily, null), false,
cache);
// Insert family into fallback map.
for (int i = 0; i < fallbackMap.size(); i++) {
@@ -145,7 +210,7 @@ public final class SystemFonts {
}
} else {
final FontFamily family = createFontFamily(fallback, languageTags, variant,
- xmlFamily.getVariableFontFamilyType(), false, cache);
+ resolveVarFamilyType(xmlFamily, name), false, cache);
if (family != null) {
familyListSet.familyList.add(family);
} else if (defaultFamily != null) {
@@ -217,7 +282,8 @@ public final class SystemFonts {
final FontFamily family = createFontFamily(
xmlFamily.getFontList(),
xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(),
- xmlFamily.getVariableFontFamilyType(),
+ resolveVarFamilyType(xmlFamily,
+ null /* all fonts under named family should be treated as default */),
true, // named family is always default
bufferCache);
if (family == null) {
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 4cdc06a999a7..a12fa5fe26ff 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -39,6 +39,7 @@ filegroup {
}
// Sources that have no dependencies that can be used directly downstream of this library
+// TODO(b/322791067): move these sources to WindowManager-Shell-shared
filegroup {
name: "wm_shell_util-sources",
srcs: [
@@ -137,6 +138,12 @@ java_library {
},
}
+java_library {
+ name: "WindowManager-Shell-shared",
+
+ srcs: ["shared/**/*.java"],
+}
+
android_library {
name: "WindowManager-Shell",
srcs: [
@@ -162,6 +169,7 @@ android_library {
"com_android_wm_shell_flags_lib",
"com.android.window.flags.window-aconfig-java",
"WindowManager-Shell-proto",
+ "WindowManager-Shell-shared",
"perfetto_trace_java_protos",
"dagger2",
"jsr330",
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 0e046581cb48..9a66c0fa9eb9 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -30,13 +30,6 @@ flag {
}
flag {
- name: "enable_pip_ui_state_on_entering"
- namespace: "multitasking"
- description: "Enables PiP UI state callback on entering"
- bug: "303718131"
-}
-
-flag {
name: "enable_pip2_implementation"
namespace: "multitasking"
description: "Enables the new implementation of PiP (PiP2)"
@@ -57,3 +50,17 @@ flag {
description: "Enables new animations for expand and collapse for bubbles"
bug: "311450609"
}
+
+flag {
+ name: "enable_pip_umo_experience"
+ namespace: "multitasking"
+ description: "Enables new UMO experience for PiP menu"
+ bug: "307998712"
+}
+
+flag {
+ name: "enable_bubble_bar"
+ namespace: "multitasking"
+ description: "Enables the new bubble bar UI for tablets"
+ bug: "286246694"
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/CounterRotator.java
index 7e95814c06c2..fd3a749af284 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/CounterRotator.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared;
import android.graphics.Point;
import android.util.RotationUtils;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 936faa3ee6bf..dcd4062cb819 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.RemoteAnimationTarget.MODE_CHANGING;
@@ -28,14 +28,13 @@ import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -53,6 +52,8 @@ import java.util.function.Predicate;
/** Various utility functions for transitions. */
public class TransitionUtil {
+ /** Flag applied to a transition change to identify it as a divider bar for animation. */
+ public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
/** @return true if the transition was triggered by opening something vs closing something */
public static boolean isOpeningType(@WindowManager.TransitionType int type) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 8241e1a481ee..8d30db64a3e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -29,7 +29,7 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/**
* Wrapper to handle the ActivityEmbedding animation update in one
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 44ee5615ac74..539832e3cf3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -46,7 +46,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter;
import com.android.wm.shell.common.ScreenshotUtils;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index efa5a1a64ade..0272f1cda6ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -38,7 +38,7 @@ import android.view.animation.TranslateAnimation;
import android.window.TransitionInfo;
import com.android.internal.policy.TransitionAnimation;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/** Animation spec for ActivityEmbedding transition. */
// TODO(b/206557124): provide an easier way to customize animation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index b4e852cfaa48..1f9358e2aa91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -38,9 +38,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.List;
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 945b8ac59b98..aea3ca1576e2 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
@@ -173,7 +173,7 @@ public class BubbleController implements ConfigurationChangeListener,
private final Context mContext;
private final BubblesImpl mImpl = new BubblesImpl();
private Bubbles.BubbleExpandListener mExpandListener;
- @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
+ @Nullable private final BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
private final FloatingContentCoordinator mFloatingContentCoordinator;
private final BubbleDataRepository mDataRepository;
private final WindowManagerShellWrapper mWindowManagerShellWrapper;
@@ -197,12 +197,12 @@ public class BubbleController implements ConfigurationChangeListener,
private final Handler mMainHandler;
private final ShellExecutor mBackgroundExecutor;
- private BubbleLogger mLogger;
- private BubbleData mBubbleData;
+ private final BubbleLogger mLogger;
+ private final BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
@Nullable private BubbleBarLayerView mLayerView;
private BubbleIconFactory mBubbleIconFactory;
- private BubblePositioner mBubblePositioner;
+ private final BubblePositioner mBubblePositioner;
private Bubbles.SysuiProxy mSysuiProxy;
// Tracks the id of the current (foreground) user.
@@ -232,13 +232,17 @@ public class BubbleController implements ConfigurationChangeListener,
/** Whether or not the BubbleStackView has been added to the WindowManager. */
private boolean mAddedToWindowManager = false;
- /** Saved screen density, used to detect display size changes in {@link #onConfigChanged}. */
+ /**
+ * Saved screen density, used to detect display size changes in {@link #onConfigurationChanged}.
+ */
private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
- /** Saved screen bounds, used to detect screen size changes in {@link #onConfigChanged}. **/
- private Rect mScreenBounds = new Rect();
+ /**
+ * Saved screen bounds, used to detect screen size changes in {@link #onConfigurationChanged}.
+ */
+ private final Rect mScreenBounds = new Rect();
- /** Saved font scale, used to detect font size changes in {@link #onConfigChanged}. */
+ /** Saved font scale, used to detect font size changes in {@link #onConfigurationChanged}. */
private float mFontScale = 0;
/** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */
@@ -253,9 +257,9 @@ public class BubbleController implements ConfigurationChangeListener,
private boolean mIsStatusBarShade = true;
/** One handed mode controller to register transition listener. */
- private Optional<OneHandedController> mOneHandedOptional;
+ private final Optional<OneHandedController> mOneHandedOptional;
/** Drag and drop controller to register listener for onDragStarted. */
- private Optional<DragAndDropController> mDragAndDropController;
+ private final Optional<DragAndDropController> mDragAndDropController;
/** Used to send bubble events to launcher. */
private Bubbles.BubbleStateListener mBubbleStateListener;
@@ -731,9 +735,11 @@ public class BubbleController implements ConfigurationChangeListener,
}
} else {
if (mStackView == null) {
+ BubbleStackViewManager bubbleStackViewManager =
+ BubbleStackViewManager.fromBubbleController(this);
mStackView = new BubbleStackView(
- mContext, this, mBubbleData, mSurfaceSynchronizer,
- mFloatingContentCoordinator, this, mMainExecutor);
+ mContext, bubbleStackViewManager, mBubblePositioner, mBubbleData,
+ mSurfaceSynchronizer, mFloatingContentCoordinator, this, mMainExecutor);
mStackView.onOrientationChanged();
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
@@ -893,7 +899,6 @@ public class BubbleController implements ConfigurationChangeListener,
* Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
* added in the meantime.
*/
- @VisibleForTesting
public void onAllBubblesAnimatedOut() {
if (mStackView != null) {
mStackView.setVisibility(INVISIBLE);
@@ -1047,7 +1052,6 @@ public class BubbleController implements ConfigurationChangeListener,
return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow();
}
- @VisibleForTesting
public boolean isStackExpanded() {
return mBubbleData.isExpanded();
}
@@ -1105,9 +1109,8 @@ public class BubbleController implements ConfigurationChangeListener,
* <p>This is used by external callers (launcher).
*/
@VisibleForTesting
- public void expandStackAndSelectBubbleFromLauncher(String key, int bubbleBarOffsetX,
- int bubbleBarOffsetY) {
- mBubblePositioner.setBubbleBarPosition(bubbleBarOffsetX, bubbleBarOffsetY);
+ public void expandStackAndSelectBubbleFromLauncher(String key, Rect bubbleBarBounds) {
+ mBubblePositioner.setBubbleBarPosition(bubbleBarBounds);
if (BubbleOverflow.KEY.equals(key)) {
mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
@@ -1273,9 +1276,17 @@ public class BubbleController implements ConfigurationChangeListener,
mBubbleData.setExpanded(true);
}
} else {
- // App bubble does not exist, lets add and expand it
- Log.i(TAG, " showOrHideAppBubble, creating and expanding app bubble");
- Bubble b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
+ // Check if it exists in the overflow
+ Bubble b = mBubbleData.getOverflowBubbleWithKey(appBubbleKey);
+ if (b != null) {
+ // It's in the overflow, so remove it & reinflate
+ Log.i(TAG, " showOrHideAppBubble, expanding app bubble from overflow");
+ mBubbleData.removeOverflowBubble(b);
+ } else {
+ // App bubble does not exist, lets add and expand it
+ Log.i(TAG, " showOrHideAppBubble, creating and expanding app bubble");
+ b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
+ }
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
}
@@ -2168,10 +2179,10 @@ public class BubbleController implements ConfigurationChangeListener,
}
@Override
- public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
+ public void showBubble(String key, Rect bubbleBarBounds) {
mMainExecutor.execute(
() -> mController.expandStackAndSelectBubbleFromLauncher(
- key, bubbleBarOffsetX, bubbleBarOffsetY));
+ key, bubbleBarBounds));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 6e0c804f0676..127c7e8d9378 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -491,6 +491,19 @@ public class BubbleData {
}
/**
+ * Explicitly removes a bubble from the overflow, if it exists.
+ *
+ * @param bubble the bubble to remove.
+ */
+ public void removeOverflowBubble(Bubble bubble) {
+ if (bubble == null) return;
+ if (mOverflowBubbles.remove(bubble)) {
+ mStateChange.removedOverflowBubble = bubble;
+ dispatchPendingChanges();
+ }
+ }
+
+ /**
* Adds a group key indicating that the summary for this group should be suppressed.
*
* @param groupKey the group key of the group whose summary should be suppressed.
@@ -1145,7 +1158,6 @@ public class BubbleData {
return null;
}
- @VisibleForTesting(visibility = PRIVATE)
public Bubble getOverflowBubbleWithKey(String key) {
for (int i = 0; i < mOverflowBubbles.size(); i++) {
Bubble bubble = mOverflowBubbles.get(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index a76bd269f31b..d62c86ce386f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -19,7 +19,6 @@ package com.android.wm.shell.bubbles;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
-import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -97,7 +96,7 @@ public class BubblePositioner {
private PointF mRestingStackPosition;
private boolean mShowingInBubbleBar;
- private final Point mBubbleBarPosition = new Point();
+ private final Rect mBubbleBarBounds = new Rect();
public BubblePositioner(Context context, WindowManager windowManager) {
mContext = context;
@@ -791,15 +790,10 @@ public class BubblePositioner {
}
/**
- * Sets the position of the bubble bar in screen coordinates.
- *
- * @param offsetX the offset of the bubble bar from the edge of the screen on the X axis
- * @param offsetY the offset of the bubble bar from the edge of the screen on the Y axis
+ * Sets the position of the bubble bar in display coordinates.
*/
- public void setBubbleBarPosition(int offsetX, int offsetY) {
- mBubbleBarPosition.set(
- getAvailableRect().width() - offsetX,
- getAvailableRect().height() + mInsets.top + mInsets.bottom - offsetY);
+ public void setBubbleBarPosition(Rect bubbleBarBounds) {
+ mBubbleBarBounds.set(bubbleBarBounds);
}
/**
@@ -820,7 +814,7 @@ public class BubblePositioner {
/** The bottom position of the expanded view when showing above the bubble bar. */
public int getExpandedViewBottomForBubbleBar() {
- return mBubbleBarPosition.y - mExpandedViewPadding;
+ return mBubbleBarBounds.top - mExpandedViewPadding;
}
/**
@@ -831,9 +825,9 @@ public class BubblePositioner {
}
/**
- * Returns the on screen co-ordinates of the bubble bar.
+ * Returns the display coordinates of the bubble bar.
*/
- public Point getBubbleBarPosition() {
- return mBubbleBarPosition;
+ public Rect getBubbleBarBounds() {
+ return mBubbleBarBounds;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index a619401301aa..9facef36a74e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -204,7 +204,7 @@ public class BubbleStackView extends FrameLayout
Choreographer.getInstance().postFrameCallback(frameCallback);
}
};
- private final BubbleController mBubbleController;
+ private final BubbleStackViewManager mManager;
private final BubbleData mBubbleData;
private final Bubbles.SysuiProxy.Provider mSysuiProxyProvider;
private StackViewState mStackViewState = new StackViewState();
@@ -858,6 +858,7 @@ public class BubbleStackView extends FrameLayout
private BubbleOverflow mBubbleOverflow;
private StackEducationView mStackEduView;
+ private StackEducationView.Manager mStackEducationViewManager;
private ManageEducationView mManageEduView;
private DismissView mDismissView;
@@ -873,15 +874,16 @@ public class BubbleStackView extends FrameLayout
private BubblePositioner mPositioner;
@SuppressLint("ClickableViewAccessibility")
- public BubbleStackView(Context context, BubbleController bubbleController,
- BubbleData data, @Nullable SurfaceSynchronizer synchronizer,
+ public BubbleStackView(Context context, BubbleStackViewManager bubbleStackViewManager,
+ BubblePositioner bubblePositioner, BubbleData data,
+ @Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
Bubbles.SysuiProxy.Provider sysuiProxyProvider,
ShellExecutor mainExecutor) {
super(context);
mMainExecutor = mainExecutor;
- mBubbleController = bubbleController;
+ mManager = bubbleStackViewManager;
mBubbleData = data;
mSysuiProxyProvider = sysuiProxyProvider;
@@ -893,7 +895,7 @@ public class BubbleStackView extends FrameLayout
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
- mPositioner = mBubbleController.getPositioner();
+ mPositioner = bubblePositioner;
final TypedArray ta = mContext.obtainStyledAttributes(
new int[]{android.R.attr.dialogCornerRadius});
@@ -903,7 +905,7 @@ public class BubbleStackView extends FrameLayout
final Runnable onBubbleAnimatedOut = () -> {
if (getBubbleCount() == 0) {
mExpandedViewTemporarilyHidden = false;
- mBubbleController.onAllBubblesAnimatedOut();
+ mManager.onAllBubblesAnimatedOut();
}
};
mStackAnimationController = new StackAnimationController(
@@ -1383,7 +1385,9 @@ public class BubbleStackView extends FrameLayout
return false;
}
if (mStackEduView == null) {
- mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
+ mStackEducationViewManager = mManager::updateWindowFlagsForBackpress;
+ mStackEduView =
+ new StackEducationView(mContext, mPositioner, mStackEducationViewManager);
addView(mStackEduView);
}
return showStackEdu();
@@ -1412,7 +1416,9 @@ public class BubbleStackView extends FrameLayout
private void updateUserEdu() {
if (isStackEduVisible() && !mStackEduView.isHiding()) {
removeView(mStackEduView);
- mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
+ mStackEducationViewManager = mManager::updateWindowFlagsForBackpress;
+ mStackEduView =
+ new StackEducationView(mContext, mPositioner, mStackEducationViewManager);
addView(mStackEduView);
showStackEdu();
}
@@ -2106,7 +2112,7 @@ public class BubbleStackView extends FrameLayout
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
logBubbleEvent(mExpandedBubble,
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
- mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> {
+ mManager.checkNotificationPanelExpandedState(notifPanelExpanded -> {
if (!notifPanelExpanded && mIsExpanded) {
startMonitoringSwipeUpGesture();
}
@@ -2227,7 +2233,7 @@ public class BubbleStackView extends FrameLayout
*/
void hideCurrentInputMethod() {
mPositioner.setImeVisible(false, 0);
- mBubbleController.hideCurrentInputMethod();
+ mManager.hideCurrentInputMethod();
}
/** Set the stack position to whatever the positioner says. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt
new file mode 100644
index 000000000000..fb597a05663b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.bubbles
+
+import java.util.function.Consumer
+
+/** Defines callbacks from [BubbleStackView] to its manager. */
+interface BubbleStackViewManager {
+
+ /** Notifies that all bubbles animated out. */
+ fun onAllBubblesAnimatedOut()
+
+ /** Notifies whether backpress should be intercepted. */
+ fun updateWindowFlagsForBackpress(interceptBack: Boolean)
+
+ /**
+ * Checks the current expansion state of the notification panel, and invokes [callback] with the
+ * result.
+ */
+ fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>)
+
+ /** Requests to hide the current input method. */
+ fun hideCurrentInputMethod()
+
+ companion object {
+
+ @JvmStatic
+ fun fromBubbleController(controller: BubbleController) = object : BubbleStackViewManager {
+ override fun onAllBubblesAnimatedOut() {
+ controller.onAllBubblesAnimatedOut()
+ }
+
+ override fun updateWindowFlagsForBackpress(interceptBack: Boolean) {
+ controller.updateWindowFlagsForBackpress(interceptBack)
+ }
+
+ override fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) {
+ controller.isNotificationPanelExpanded(callback)
+ }
+
+ override fun hideCurrentInputMethod() {
+ controller.hideCurrentInputMethod()
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
index 9e8a385262e4..c1f704ab455d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
@@ -24,8 +24,8 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
/**
* Observer used to identify tasks that are opening or moving to front. If a bubble activity is
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 5776ad109d19..7a5afec934f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -17,6 +17,7 @@
package com.android.wm.shell.bubbles;
import android.content.Intent;
+import android.graphics.Rect;
import com.android.wm.shell.bubbles.IBubblesListener;
/**
@@ -29,7 +30,7 @@ interface IBubbles {
oneway void unregisterBubbleListener(in IBubblesListener listener) = 2;
- oneway void showBubble(in String key, in int bubbleBarOffsetX, in int bubbleBarOffsetY) = 3;
+ oneway void showBubble(in String key, in Rect bubbleBarBounds) = 3;
oneway void removeBubble(in String key) = 4;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 95f101722e89..c4108c4129e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -35,7 +35,7 @@ import com.android.wm.shell.animation.Interpolators
class StackEducationView(
context: Context,
private val positioner: BubblePositioner,
- private val controller: BubbleController
+ private val manager: Manager
) : LinearLayout(context) {
companion object {
@@ -44,6 +44,12 @@ class StackEducationView(
private const val ANIMATE_DURATION_SHORT: Long = 40
}
+ /** Callbacks to notify managers of [StackEducationView] about events. */
+ interface Manager {
+ /** Notifies whether backpress should be intercepted. */
+ fun updateWindowFlagsForBackpress(interceptBack: Boolean)
+ }
+
private val view by lazy { requireViewById<View>(R.id.stack_education_layout) }
private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) }
private val descTextView by lazy { requireViewById<TextView>(R.id.stack_education_description) }
@@ -93,7 +99,7 @@ class StackEducationView(
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
setOnKeyListener(null)
- controller.updateWindowFlagsForBackpress(false /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(false /* interceptBack */)
}
private fun setTextColor() {
@@ -124,7 +130,7 @@ class StackEducationView(
isHiding = false
if (visibility == VISIBLE) return false
- controller.updateWindowFlagsForBackpress(true /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(true /* interceptBack */)
layoutParams.width =
if (positioner.isLargeScreen || positioner.isLandscape)
context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
@@ -185,7 +191,7 @@ class StackEducationView(
if (visibility != VISIBLE || isHiding) return
isHiding = true
- controller.updateWindowFlagsForBackpress(false /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(false /* interceptBack */)
animate()
.alpha(0f)
.setDuration(if (isExpanding) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 893a87fe4885..84a616f1e1a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -22,6 +22,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
+import android.graphics.Rect;
import android.util.Log;
import android.util.Size;
import android.view.View;
@@ -149,12 +150,12 @@ public class BubbleBarAnimationHelper {
bbev.setVisibility(VISIBLE);
// Set the pivot point for the scale, so the view animates out from the bubble bar.
- Point bubbleBarPosition = mPositioner.getBubbleBarPosition();
+ Rect bubbleBarBounds = mPositioner.getBubbleBarBounds();
mExpandedViewContainerMatrix.setScale(
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
- bubbleBarPosition.x,
- bubbleBarPosition.y);
+ bubbleBarBounds.centerX(),
+ bubbleBarBounds.top);
bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index 49db8d9c54a6..e8c809e5db4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -20,10 +20,11 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import android.annotation.IntDef;
+import com.android.wm.shell.shared.TransitionUtil;
+
/** Helper utility class of methods and constants that are available to be imported in Launcher. */
public class SplitScreenConstants {
/** Duration used for every split fade-in or fade-out. */
@@ -126,7 +127,7 @@ public class SplitScreenConstants {
WINDOWING_MODE_FREEFORM};
/** Flag applied to a transition change to identify it as a divider bar for animation. */
- public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
+ public static final int FLAG_IS_DIVIDER_BAR = TransitionUtil.FLAG_IS_DIVIDER_BAR;
public static final String splitPositionToString(@SplitPosition int pos) {
switch (pos) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index c0fc02fadd4d..f82212d97bd3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -86,10 +86,10 @@ class DesktopModeTaskRepository {
) {
visibleTasksListeners[visibleTasksListener] = executor
displayData.keyIterator().forEach { displayId ->
- val visibleTasks = getVisibleTaskCount(displayId)
+ val visibleTasksCount = getVisibleTaskCount(displayId)
val stashed = isStashed(displayId)
executor.execute {
- visibleTasksListener.onVisibilityChanged(displayId, visibleTasks > 0)
+ visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTasksCount)
visibleTasksListener.onStashedChanged(displayId, stashed)
}
}
@@ -222,10 +222,8 @@ class DesktopModeTaskRepository {
val otherDisplays = displayData.keyIterator().asSequence().filter { it != displayId }
for (otherDisplayId in otherDisplays) {
if (displayData[otherDisplayId].visibleTasks.remove(taskId)) {
- // Task removed from other display, check if we should notify listeners
- if (displayData[otherDisplayId].visibleTasks.isEmpty()) {
- notifyVisibleTaskListeners(otherDisplayId, hasVisibleFreeformTasks = false)
- }
+ notifyVisibleTaskListeners(otherDisplayId,
+ displayData[otherDisplayId].visibleTasks.size)
}
}
}
@@ -248,15 +246,15 @@ class DesktopModeTaskRepository {
)
}
- // Check if count changed and if there was no tasks or this is the first task
- if (prevCount != newCount && (prevCount == 0 || newCount == 0)) {
- notifyVisibleTaskListeners(displayId, newCount > 0)
+ // Check if count changed
+ if (prevCount != newCount) {
+ notifyVisibleTaskListeners(displayId, newCount)
}
}
- private fun notifyVisibleTaskListeners(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+ private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
visibleTasksListeners.forEach { (listener, executor) ->
- executor.execute { listener.onVisibilityChanged(displayId, hasVisibleFreeformTasks) }
+ executor.execute { listener.onTasksVisibilityChanged(displayId, visibleTasksCount) }
}
}
@@ -379,9 +377,9 @@ class DesktopModeTaskRepository {
*/
interface VisibleTasksListener {
/**
- * Called when the desktop starts or stops showing freeform tasks.
+ * Called when the desktop changes the number of visible freeform tasks.
*/
- fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {}
+ fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {}
/**
* Called when the desktop stashed status changes.
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 28c06a46c516..a089e81ff6dd 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
@@ -106,8 +106,8 @@ class DesktopTasksController(
visualIndicator = null
}
private val taskVisibilityListener = object : VisibleTasksListener {
- override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
- launchAdjacentController.launchAdjacentEnabled = !hasVisibleFreeformTasks
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
+ launchAdjacentController.launchAdjacentEnabled = visibleTasksCount == 0
}
}
private val dragToDesktopStateListener = object : DragToDesktopStateListener {
@@ -1033,14 +1033,16 @@ class DesktopTasksController(
SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>
private val listener: VisibleTasksListener = object : VisibleTasksListener {
- override fun onVisibilityChanged(displayId: Int, visible: Boolean) {
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "IDesktopModeImpl: onVisibilityChanged display=%d visible=%b",
+ "IDesktopModeImpl: onVisibilityChanged display=%d visible=%d",
displayId,
- visible
+ visibleTasksCount
)
- remoteListener.call { l -> l.onVisibilityChanged(displayId, visible) }
+ remoteListener.call {
+ l -> l.onTasksVisibilityChanged(displayId, visibleTasksCount)
+ }
}
override fun onStashedChanged(displayId: Int, stashed: Boolean) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 731fec7899ae..39610e3c5c80 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -26,6 +26,7 @@ import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
@@ -33,7 +34,6 @@ import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.util.KtProtoLog
-import com.android.wm.shell.util.TransitionUtil
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
index 39128a863ec9..8ed87f23bf40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -22,8 +22,8 @@ package com.android.wm.shell.desktopmode;
*/
interface IDesktopTaskListener {
- /** Desktop task visibility has change. Visible if at least 1 task is visible. */
- oneway void onVisibilityChanged(int displayId, boolean visible);
+ /** Desktop tasks visibility has changed. Visible if at least 1 task is visible. */
+ oneway void onTasksVisibilityChanged(int displayId, int visibleTasksCount);
/** Desktop task stashed status has changed. */
oneway void onStashedChanged(int displayId, boolean stashed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
index 73a7348d5aca..3fad28ad232f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
@@ -15,4 +15,4 @@ particular order):
Todo
- Per-feature docs
- Feature flagging
-- Best practices \ No newline at end of file
+- Best practices & patterns \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index fbf326eadcd5..9aa5f4ffcd78 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -102,5 +102,5 @@ AIDL interfaces and constants. Currently, all AIDL files, and classes under the
Launcher uses.
If the new code doesn't fall into those categories, they can be added explicitly in the Shell's
-[Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp) file under the
+[Android.bp](/libs/WindowManager/Shell/Android.bp) file under the
`wm_shell_util-sources` filegroup. \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
index 6c01d962adc9..7070dead9957 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
@@ -21,16 +21,16 @@ developers to jump into a few select files and understand how different componen
(especially as products override components).
The module dependency tree looks a bit like:
-- [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
+- [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
(provides threading-related components)
- - [WMShellBaseModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
+ - [WMShellBaseModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
(provides components that are likely common to all products, ie. DisplayController,
Transactions, etc.)
- - [WMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
+ - [WMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
(phone/tablet specific components only)
- - [TvPipModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
+ - [TvPipModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
(PIP specific components for TV)
- - [TvWMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
+ - [TvWMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
(TV specific components only)
- etc.
@@ -43,7 +43,7 @@ In some rare cases, there are base components that can change behavior depending
product it runs on. If there are hooks that can be added to the component, that is the
preferable approach.
-The alternative is to use the [@DynamicOverride](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
+The alternative is to use the [@DynamicOverride](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
annotation to allow the product module to provide an implementation that the base module can
reference. This is most useful if the existence of the entire component is controlled by the
product and the override implementation is optional (there is a default implementation). More
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index f9ea1d4e2a07..438aa768165e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -29,30 +29,78 @@ building to check the log state (is enabled) before printing the print format st
### Kotlin
Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)).
-For logging in Kotlin, use the [KtProtoLog](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
+For logging in Kotlin, use the [KtProtoLog](/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
class which has a similar API to the Java ProtoLog class.
### Enabling ProtoLog command line logging
-Run these commands to enable protologs for both WM Core and WM Shell to print to logcat.
+Run these commands to enable protologs (in logcat) for WM Core ([list of all core tags](/core/java/com/android/internal/protolog/ProtoLogGroup.java)):
```shell
-adb shell wm logging enable-text NEW_FEATURE
-adb shell wm logging disable-text NEW_FEATURE
+adb shell wm logging enable-text TAG
+adb shell wm logging disable-text TAG
+```
+
+And these commands to enable protologs (in logcat) for WM Shell ([list of all shell tags](/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java)):
+```shell
+adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
+adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
```
## Winscope Tracing
The Winscope tool is extremely useful in determining what is happening on-screen in both
WindowManager and SurfaceFlinger. Follow [go/winscope](http://go/winscope-help) to learn how to
-use the tool.
+use the tool. This trace will contain all the information about the windows/activities/surfaces on
+screen.
+
+## WindowManager/SurfaceFlinger hierarchy dump
+
+A quick way to view the WindowManager hierarchy without a winscope trace is via the wm dumps:
+```shell
+adb shell dumpsys activity containers
+```
+
+Likewise, the SurfaceFlinger hierarchy can be dumped for inspection by running:
+```shell
+adb shell dumpsys SurfaceFlinger
+# Search output for "Layer Hierarchy"
+```
+
+## Tracing global SurfaceControl transaction updates
-In addition, there is limited preliminary support for Winscope tracing componetns in the Shell,
-which involves adding trace fields to [wm_shell_trace.proto](frameworks/base/libs/WindowManager/Shell/proto/wm_shell_trace.proto)
-file and ensure it is updated as a part of `WMShell#writeToProto`.
+While Winscope traces are very useful, it sometimes doesn't give you enough information about which
+part of the code is initiating the transaction updates. In such cases, it can be helpful to get
+stack traces when specific surface transaction calls are made, which is possible by enabling the
+following system properties for example:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha # matches the name of the SurfaceControlTransaction method
+adb shell setprop persist.wm.debug.sc.tx.log_match_name com.android.systemui # matches the name of the surface
+adb reboot
+adb logcat -s "SurfaceControlRegistry"
+
+# Disabling logging
+adb shell setprop persist.wm.debug.sc.tx.log_match_call \"\"
+adb shell setprop persist.wm.debug.sc.tx.log_match_name \"\"
+adb reboot
+```
+
+It is not necessary to set both `log_match_call` and `log_match_name`, but note logs can be quite
+noisy if unfiltered.
-Tracing can be started via the shell command (to be added to the Winscope tool as needed):
+## Tracing activity starts in the app process
+
+It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
+(ie. if you are repro'ing a bug related to activity starts). You can enable this system property to
+get this trace:
```shell
-adb shell cmd statusbar tracing start
-adb shell cmd statusbar tracing stop
+# Enabling
+adb shell setprop persist.wm.debug.start_activity true
+adb reboot
+adb logcat -s "Instrumentation"
+
+# Disabling
+adb shell setprop persist.wm.debug.start_activity \"\"
+adb reboot
```
## Dumps
@@ -69,6 +117,21 @@ If information should be added to the dump, either:
- Update `WMShell` if you are dumping SysUI state
- Inject `ShellCommandHandler` into your Shell class, and add a dump callback
+## Shell commands
+
+It can be useful to add additional shell commands to drive and test specific interactions.
+
+To add a new command for your feature, inject a `ShellCommandHandler` into your class and add a
+shell command handler in your controller.
+
+```shell
+# List all available commands
+adb shell dumpsys activity service SystemUIService WMShell help
+
+# Run a specific command
+adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
+```
+
## Debugging in Android Studio
If you are using the [go/sysui-studio](http://go/sysui-studio) project, then you can debug Shell
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
index a88ef6aea2ec..b489fe8ea1a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
@@ -19,25 +19,24 @@ Currently, the WMShell library is used to drive the windowing experience on hand
## Where does the code live
-The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](frameworks/base/libs/WindowManager/Shell)
+The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](/libs/WindowManager/Shell)
directory and is included as a part dependency of the host SystemUI apk.
## How do I build the Shell library
-The library can be built directly by running (using [go/makepush](http://go/makepush)):
+The library can be built directly by running:
```shell
-mp :WindowManager-Shell
+m WindowManager-Shell
```
But this is mainly useful for inspecting the contents of the library or verifying it builds. The
-various targets can be found in the Shell library's [Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp)
+various targets can be found in the Shell library's [Android.bp](/libs/WindowManager/Shell/Android.bp)
file.
Normally, you would build it as a part of the host SystemUI, for example via commandline:
```shell
# Phone SystemUI variant
-mp sysuig
-# Building Shell & SysUI changes along w/ framework changes
-mp core services sysuig
+m SystemUI
+adevice update
```
Or preferably, if you are making WMShell/SysUI only changes (no other framework changes), then
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md
new file mode 100644
index 000000000000..0e20a8ec275c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md
@@ -0,0 +1,30 @@
+# Pattern (one line description)
+
+### What this pattern solves
+
+Give detailed information about the problem that this pattern addresses, include when it
+should/should not be used.
+
+### How it works
+
+Give a high level overview of how this pattern works technically with sufficient links for the
+relevant dependencies for folks to read more.
+
+### Code examples
+
+Explain how this pattern is used in code.
+
+File.kt:
+```kotlin
+fun someFunction() {
+ // Use this code
+}
+```
+
+### Relevant links
+
+Add relevant links to other files that implement this pattern, design docs, or other important
+info.
+
+Link 1: [More info](some_example_link) \
+Link 2: [More info](some_example_link) \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
index d6302e640ba7..30ff6691f503 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
@@ -80,4 +80,6 @@ adb shell dumpsys activity service SystemUIService WMShell
# Run a specific command
adb shell dumpsys activity service SystemUIService WMShell help
adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
-``` \ No newline at end of file
+```
+
+More detail can be found in [Debugging in the Shell](debugging.md) section. \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
index 8a80333facc4..98af930c4486 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
@@ -5,7 +5,7 @@
## Unit tests
New WM Shell unit tests can be added to the
-[Shell/tests/unittest](frameworks/base/libs/WindowManager/Shell/tests/unittest) directory, and can
+[Shell/tests/unittest](/libs/WindowManager/Shell/tests/unittest) directory, and can
be run via command line using `atest`:
```shell
atest WMShellUnitTests
@@ -25,10 +25,24 @@ Flicker tests are tests that perform actions and make assertions on the state in
and SurfaceFlinger traces captured during the run.
New WM Shell Flicker tests can be added to the
-[Shell/tests/flicker](frameworks/base/libs/WindowManager/Shell/tests/flicker) directory, and can
-be run via command line using `atest`:
+[Shell/tests/flicker](/libs/WindowManager/Shell/tests/flicker) directory, and can be run via command line using `atest`:
```shell
-atest WMShellFlickerTests
+# Bubbles
+atest WMShellFlickerTestsBubbles
+
+# PIP
+atest WMShellFlickerTestsPip1
+atest WMShellFlickerTestsPip2
+atest WMShellFlickerTestsPip3
+atest WMShellFlickerTestsPipApps
+atest WMShellFlickerTestsPipAppsCSuite
+
+# Splitscreen
+atest WMShellFlickerTestsSplitScreenGroup1
+atest WMShellFlickerTestsSplitScreenGroup2
+
+# Other
+atest WMShellFlickerTestsOther
```
**Note**: Currently Flicker tests can only be run from the commandline and not via SysUI Studio
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
index eac748894432..9d015357b60b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
@@ -43,7 +43,7 @@ the product.
## Dagger setup
-The threading-related components are provided by the [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
+The threading-related components are provided by the [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
for example, the Executors and Handlers for the various threads that are used. You can request
an executor of the necessary type by using the appropriate annotation for each of the threads (ie.
`@ShellMainThread Executor`) when injecting into your Shell component.
@@ -76,7 +76,7 @@ To get the SysUI main thread, you can use the `@Main` annotation.
want to dedupe multiple messages
- In such cases inject `@ShellMainThread Handler` or use view.getHandler() which should be OK
assuming that the view root was initialized on the main Shell thread
-- **Never use Looper.getMainLooper()**
+- <u>**Never</u> use Looper.getMainLooper()**
- It's likely going to be wrong, you can inject `@Main ShellExecutor` to get the SysUI main thread
### Testing
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 e63bbc07bc41..73de231fb63a 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
@@ -27,7 +27,7 @@ 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 com.android.wm.shell.util.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index e7392662bdf1..896ca9647720 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -71,11 +71,11 @@ import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.CounterRotatorHelper;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 238e6b5bf2af..c5a01025dcdd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -797,14 +797,20 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsAlgorithm.getMovementBounds(postChangeBounds),
mPipBoundsState.getStashedState());
- updateDisplayLayout.run();
+ // Scale PiP on density dpi change, so it appears to be the same size physically.
+ final boolean densityDpiChanged =
+ mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
+ && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
+ != layout.densityDpi());
+ if (densityDpiChanged) {
+ final float scale = (float) layout.densityDpi()
+ / mPipDisplayLayoutState.getDisplayLayout().densityDpi();
+ postChangeBounds.set(0, 0,
+ (int) (postChangeBounds.width() * scale),
+ (int) (postChangeBounds.height() * scale));
+ }
- // Resize the PiP bounds to be at the same scale relative to the new size spec. For
- // example, if PiP was resized to 90% of the maximum size on the previous layout,
- // make sure it is 90% of the new maximum size spec.
- postChangeBounds.set(0, 0,
- (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()),
- (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale()));
+ updateDisplayLayout.run();
// Calculate the PiP bounds in the new orientation based on same fraction along the
// rotated movement bounds.
@@ -821,10 +827,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.setHasUserResizedPip(true);
mTouchHandler.setUserResizeBounds(postChangeBounds);
- final boolean densityDpiChanged =
- mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
- && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
- != layout.densityDpi());
if (densityDpiChanged) {
// Using PipMotionHelper#movePip directly here may cause race condition since
// the app content in PiP mode may or may not be updated for the new density dpi.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index d16a692dbd0a..c2f4d72a1ddf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -71,9 +71,9 @@ import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
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 1232baacdac7..97d3457aaa38 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
@@ -58,10 +58,10 @@ import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 5de8a9be9576..e8894a836663 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -48,9 +48,9 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.OneShotRemoteHandler;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index af05aa2c5ec6..e5045aea189b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -44,6 +44,8 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
+import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -64,8 +66,6 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonT
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
-import static com.android.wm.shell.util.TransitionUtil.isClosingType;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -135,13 +135,13 @@ import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.common.split.SplitWindowManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.SplitBounds;
-import com.android.wm.shell.util.TransitionUtil;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import dalvik.annotation.optimization.NeverCompile;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 84f21f693eb8..198ec82b5f21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -37,8 +37,8 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.VisibleForTesting;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Objects;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
index 628ce27fe514..b03daaafd70c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
@@ -27,8 +27,8 @@ import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
-import com.android.wm.shell.util.CounterRotator;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.CounterRotator;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 8c2203ef7a49..8746b8c8d55c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -24,7 +24,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -48,11 +48,11 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.splitscreen.StageCoordinator;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Map;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 193a4fb5b503..c70a8219489e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -109,8 +109,8 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index af31f5f27fe9..cb2944c120e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -32,7 +32,7 @@ import android.window.TransitionInfo;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/**
* The {@link TransitionObserver} that observes for transitions involving the home
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 293b66084d28..4c4c5806ea55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -39,7 +39,7 @@ import androidx.annotation.BinderThread;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index b012d359931a..1be85d05c16e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -56,7 +56,7 @@ import com.android.internal.R;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/** The helper class that provides methods for adding styles to transition animations. */
public class TransitionAnimationHelper {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 67fc7e2b4ea6..5e79681e060b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -37,9 +37,9 @@ import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.wm.shell.util.TransitionUtil.isClosingType;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -80,13 +80,13 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.tracing.LegacyTransitionTracer;
import com.android.wm.shell.transition.tracing.PerfettoTransitionTracer;
import com.android.wm.shell.transition.tracing.TransitionTracer;
-import com.android.wm.shell.util.TransitionUtil;
import java.io.PrintWriter;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 98d343b66760..c26604a84a61 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -35,6 +35,7 @@ import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
@@ -43,7 +44,6 @@ import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index dab762f233e2..fa0aba5a6ee9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -1190,6 +1190,23 @@ public class BubbleDataTest extends ShellTestCase {
assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isNull();
}
+ @Test
+ public void test_removeOverflowBubble() {
+ sendUpdatedEntryAtTime(mEntryA1, 2000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ verifyUpdateReceived();
+ assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
+
+ mBubbleData.removeOverflowBubble(mBubbleA1);
+ verifyUpdateReceived();
+
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.removedOverflowBubble).isEqualTo(mBubbleA1);
+ assertOverflowChangedTo(ImmutableList.of());
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 4878df806f82..75965d6d68d9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -56,9 +56,7 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
-/**
- * Tests for loading / inflating views & icons for a bubble.
- */
+/** Tests for loading / inflating views & icons for a bubble. */
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
@@ -76,25 +74,33 @@ class BubbleViewInfoTest : ShellTestCase() {
@Before
fun setup() {
metadataFlagListener = Bubbles.BubbleMetadataFlagListener {}
- iconFactory = BubbleIconFactory(context,
+ iconFactory =
+ BubbleIconFactory(
+ context,
60,
30,
Color.RED,
- mContext.resources.getDimensionPixelSize(
- R.dimen.importance_ring_stroke_width))
+ mContext.resources.getDimensionPixelSize(R.dimen.importance_ring_stroke_width)
+ )
mainExecutor = TestShellExecutor()
val windowManager = context.getSystemService(WindowManager::class.java)
val shellInit = ShellInit(mainExecutor)
val shellCommandHandler = ShellCommandHandler()
- val shellController = ShellController(context, shellInit, shellCommandHandler,
- mainExecutor)
+ val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor)
val bubblePositioner = BubblePositioner(context, windowManager)
- val bubbleData = BubbleData(context, mock<BubbleLogger>(), bubblePositioner,
- BubbleEducationController(context), mainExecutor)
+ val bubbleData =
+ BubbleData(
+ context,
+ mock<BubbleLogger>(),
+ bubblePositioner,
+ BubbleEducationController(context),
+ mainExecutor
+ )
val surfaceSynchronizer = { obj: Runnable -> obj.run() }
- bubbleController = BubbleController(
+ bubbleController =
+ BubbleController(
context,
shellInit,
shellCommandHandler,
@@ -122,18 +128,36 @@ class BubbleViewInfoTest : ShellTestCase() {
mock<Transitions>(),
mock<SyncTransactionQueue>(),
mock<IWindowManager>(),
- mock<BubbleProperties>())
+ mock<BubbleProperties>()
+ )
- bubbleStackView = BubbleStackView(context, bubbleController, bubbleData,
- surfaceSynchronizer, FloatingContentCoordinator(), bubbleController, mainExecutor)
+ val bubbleStackViewManager = BubbleStackViewManager.fromBubbleController(bubbleController)
+ bubbleStackView =
+ BubbleStackView(
+ context,
+ bubbleStackViewManager,
+ bubblePositioner,
+ bubbleData,
+ surfaceSynchronizer,
+ FloatingContentCoordinator(),
+ bubbleController,
+ mainExecutor
+ )
bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
}
@Test
fun testPopulate() {
bubble = createBubbleWithShortcut()
- val info = BubbleViewInfoTask.BubbleViewInfo.populate(context,
- bubbleController, bubbleStackView, iconFactory, bubble, false /* skipInflation */)
+ val info =
+ BubbleViewInfoTask.BubbleViewInfo.populate(
+ context,
+ bubbleController,
+ bubbleStackView,
+ iconFactory,
+ bubble,
+ false /* skipInflation */
+ )
assertThat(info!!).isNotNull()
assertThat(info.imageView).isNotNull()
@@ -151,9 +175,15 @@ class BubbleViewInfoTest : ShellTestCase() {
@Test
fun testPopulateForBubbleBar() {
bubble = createBubbleWithShortcut()
- val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context,
- bubbleController, bubbleBarLayerView, iconFactory, bubble,
- false /* skipInflation */)
+ val info =
+ BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
+ context,
+ bubbleController,
+ bubbleBarLayerView,
+ iconFactory,
+ bubble,
+ false /* skipInflation */
+ )
assertThat(info!!).isNotNull()
assertThat(info.imageView).isNull()
@@ -176,12 +206,18 @@ class BubbleViewInfoTest : ShellTestCase() {
// exception here if the app has an issue loading the shortcut icon; we default to
// the app icon in that case / none of the icons will be null.
val mockIconFactory = mock<BubbleIconFactory>()
- whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo),
- any())).doThrow(RuntimeException())
+ whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo), any()))
+ .doThrow(RuntimeException())
- val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context,
- bubbleController, bubbleBarLayerView, iconFactory, bubble,
- true /* skipInflation */)
+ val info =
+ BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
+ context,
+ bubbleController,
+ bubbleBarLayerView,
+ iconFactory,
+ bubble,
+ true /* skipInflation */
+ )
assertThat(info).isNotNull()
assertThat(info?.shortcutInfo).isNotNull()
@@ -194,8 +230,17 @@ class BubbleViewInfoTest : ShellTestCase() {
private fun createBubbleWithShortcut(): Bubble {
val shortcutInfo = ShortcutInfo.Builder(mContext, "mockShortcutId").build()
- return Bubble("mockKey", shortcutInfo, 1000, Resources.ID_NULL,
- "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */,
- mainExecutor, metadataFlagListener)
+ return Bubble(
+ "mockKey",
+ shortcutInfo,
+ 1000,
+ Resources.ID_NULL,
+ "mockTitle",
+ 0 /* taskId */,
+ "mockLocus",
+ true /* isDismissible */,
+ mainExecutor,
+ metadataFlagListener
+ )
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 3fe78efdf2b1..445f74a52b0d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -124,7 +124,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
repo.addVisibleTasksListener(listener, executor)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
}
@@ -148,7 +148,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
repo.addVisibleTasksListener(listener, executor)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
// One call as adding listener notifies it
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(0)
}
@@ -162,8 +162,8 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
}
@Test
@@ -175,16 +175,16 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
- assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isFalse()
+ assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(0)
assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(0)
repo.updateVisibleFreeformTasks(displayId = 1, taskId = 2, visible = true)
executor.flushAll()
// Listener for secondary display is notified
- assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(1)
assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
// No changes to listener for default display
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
@@ -198,7 +198,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
// Mark task 1 visible on secondary display
repo.updateVisibleFreeformTasks(displayId = 1, taskId = 1, visible = true)
@@ -208,11 +208,11 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
// 1 - visible task added
// 2 - visible task removed
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
// Secondary display should have 1 call for visible task added
assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
- assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(1)
}
@Test
@@ -224,17 +224,17 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
executor.flushAll()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false)
executor.flushAll()
- assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4)
}
@Test
@@ -397,8 +397,8 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
}
class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener {
- var hasVisibleTasksOnDefaultDisplay = false
- var hasVisibleTasksOnSecondaryDisplay = false
+ var visibleTasksCountOnDefaultDisplay = 0
+ var visibleTasksCountOnSecondaryDisplay = 0
var visibleChangesOnDefaultDisplay = 0
var visibleChangesOnSecondaryDisplay = 0
@@ -409,14 +409,14 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
var stashedChangesOnDefaultDisplay = 0
var stashedChangesOnSecondaryDisplay = 0
- override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
when (displayId) {
DEFAULT_DISPLAY -> {
- hasVisibleTasksOnDefaultDisplay = hasVisibleFreeformTasks
+ visibleTasksCountOnDefaultDisplay = visibleTasksCount
visibleChangesOnDefaultDisplay++
}
SECOND_DISPLAY -> {
- hasVisibleTasksOnSecondaryDisplay = hasVisibleFreeformTasks
+ visibleTasksCountOnSecondaryDisplay = visibleTasksCount
visibleChangesOnSecondaryDisplay++
}
else -> fail("Visible task listener received unexpected display id: $displayId")
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 008ea3aaa2e7..14b8d8d0aa12 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -341,6 +341,10 @@ void SkiaCanvas::concat(const SkMatrix& matrix) {
mCanvas->concat(matrix);
}
+void SkiaCanvas::concat(const SkM44& matrix) {
+ mCanvas->concat(matrix);
+}
+
void SkiaCanvas::rotate(float degrees) {
mCanvas->rotate(degrees);
}
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 4bf1790e2415..5e3553bbbbb4 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -86,6 +86,7 @@ public:
virtual void getMatrix(SkMatrix* outMatrix) const override;
virtual void setMatrix(const SkMatrix& matrix) override;
virtual void concat(const SkMatrix& matrix) override;
+ virtual void concat(const SkM44& matrix) override;
virtual void rotate(float degrees) override;
virtual void scale(float sx, float sy) override;
virtual void skew(float sx, float sy) override;
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 9ec023b2c36f..20e3ad2c8202 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -175,6 +175,7 @@ public:
virtual void setMatrix(const SkMatrix& matrix) = 0;
virtual void concat(const SkMatrix& matrix) = 0;
+ virtual void concat(const SkM44& matrix) = 0;
virtual void rotate(float degrees) = 0;
virtual void scale(float sx, float sy) = 0;
virtual void skew(float sx, float sy) = 0;
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index d5725935551a..295f4dce72b5 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -158,6 +158,13 @@ static void concat(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jlong matrixHan
get_canvas(canvasHandle)->concat(*matrix);
}
+static void concat44(JNIEnv* env, jobject obj, jlong canvasHandle, jfloatArray arr) {
+ jfloat* matVals = env->GetFloatArrayElements(arr, 0);
+ const SkM44 matrix = SkM44::RowMajor(matVals);
+ get_canvas(canvasHandle)->concat(matrix);
+ env->ReleaseFloatArrayElements(arr, matVals, 0);
+}
+
static void rotate(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jfloat degrees) {
get_canvas(canvasHandle)->rotate(degrees);
}
@@ -781,6 +788,7 @@ static const JNINativeMethod gMethods[] = {
{"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
{"nSetMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
{"nConcat","(JJ)V", (void*) CanvasJNI::concat},
+ {"nConcat","(J[F)V", (void*) CanvasJNI::concat44},
{"nRotate","(JF)V", (void*) CanvasJNI::rotate},
{"nScale","(JFF)V", (void*) CanvasJNI::scale},
{"nSkew","(JFF)V", (void*) CanvasJNI::skew},
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index d55d28d469d0..b5f7caaf1b5b 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -31,6 +31,8 @@
#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
+#include <sstream>
+
#include "Properties.h"
#include "RenderThread.h"
#include "pipeline/skia/ShaderCache.h"
@@ -40,7 +42,8 @@ namespace android {
namespace uirenderer {
namespace renderthread {
-static std::array<std::string_view, 20> sEnableExtensions{
+// Not all of these are strictly required, but are all enabled if present.
+static std::array<std::string_view, 21> sEnableExtensions{
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
@@ -61,6 +64,7 @@ static std::array<std::string_view, 20> sEnableExtensions{
VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME,
+ VK_EXT_DEVICE_FAULT_EXTENSION_NAME,
};
static bool shouldEnableExtension(const std::string_view& extension) {
@@ -303,6 +307,15 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe
*tailPNext = ycbcrFeature;
tailPNext = &ycbcrFeature->pNext;
+ if (grExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
+ VkPhysicalDeviceFaultFeaturesEXT* deviceFaultFeatures =
+ new VkPhysicalDeviceFaultFeaturesEXT;
+ deviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
+ deviceFaultFeatures->pNext = nullptr;
+ *tailPNext = deviceFaultFeatures;
+ tailPNext = &deviceFaultFeatures->pNext;
+ }
+
// query to get the physical device features
mGetPhysicalDeviceFeatures2(mPhysicalDevice, &features);
// this looks like it would slow things down,
@@ -405,6 +418,79 @@ void VulkanManager::initialize() {
});
}
+namespace {
+void onVkDeviceFault(const std::string& contextLabel, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ // The final crash string should contain as much differentiating info as possible, up to 1024
+ // bytes. As this final message is constructed, the same information is also dumped to the logs
+ // but in a more verbose format. Building the crash string is unsightly, so the clearer logging
+ // statement is always placed first to give context.
+ ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", contextLabel.c_str(), description.c_str());
+ std::stringstream crashMsg;
+ crashMsg << "VK_ERROR_DEVICE_LOST (" << contextLabel;
+
+ if (!addressInfos.empty()) {
+ ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size());
+ crashMsg << ", " << addressInfos.size() << " address info (";
+ for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) {
+ ALOGE(" addressType: %d", (int)addressInfo.addressType);
+ ALOGE(" reportedAddress: %" PRIu64, addressInfo.reportedAddress);
+ ALOGE(" addressPrecision: %" PRIu64, addressInfo.addressPrecision);
+ crashMsg << addressInfo.addressType << ":"
+ << addressInfo.reportedAddress << ":"
+ << addressInfo.addressPrecision << ", ";
+ }
+ crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+ crashMsg << ")";
+ }
+
+ if (!vendorInfos.empty()) {
+ ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size());
+ crashMsg << ", " << vendorInfos.size() << " vendor info (";
+ for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) {
+ ALOGE(" description: %s", vendorInfo.description);
+ ALOGE(" vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode);
+ ALOGE(" vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData);
+ // Omit descriptions for individual vendor info structs in the crash string, as the
+ // fault code and fault data fields should be enough for clustering, and the verbosity
+ // isn't worth it. Additionally, vendors may just set the general description field of
+ // the overall fault to the description of the first element in this list, and that
+ // overall description will be placed at the end of the crash string.
+ crashMsg << vendorInfo.vendorFaultCode << ":"
+ << vendorInfo.vendorFaultData << ", ";
+ }
+ crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+ crashMsg << ")";
+ }
+
+ if (!vendorBinaryData.empty()) {
+ // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports
+ ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics"
+ " Stack team if you observe this message).",
+ vendorBinaryData.size());
+ crashMsg << ", " << vendorBinaryData.size() << " bytes binary";
+ }
+
+ crashMsg << "): " << description;
+ LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str());
+}
+
+void deviceLostProcRenderThread(void* callbackContext, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ onVkDeviceFault("RenderThread", description, addressInfos, vendorInfos, vendorBinaryData);
+}
+void deviceLostProcUploadThread(void* callbackContext, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ onVkDeviceFault("UploadThread", description, addressInfos, vendorInfos, vendorBinaryData);
+}
+} // anonymous namespace
+
static void onGrContextReleased(void* context) {
VulkanManager* manager = (VulkanManager*)context;
manager->decStrong((void*)onGrContextReleased);
@@ -430,6 +516,10 @@ sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options,
backendContext.fVkExtensions = &mExtensions;
backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
backendContext.fGetProc = std::move(getProc);
+ backendContext.fDeviceLostContext = nullptr;
+ backendContext.fDeviceLostProc = (contextType == ContextType::kRenderThread)
+ ? deviceLostProcRenderThread
+ : deviceLostProcUploadThread;
LOG_ALWAYS_FATAL_IF(options.fContextDeleteProc != nullptr, "Conflicting fContextDeleteProcs!");
this->incStrong((void*)onGrContextReleased);
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
index 461dafb91916..ec1edb8943af 100644
--- a/location/java/android/location/altitude/AltitudeConverter.java
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -219,6 +219,27 @@ public final class AltitudeConverter {
* called on the main thread as data will not be loaded from raw assets. Returns true if a Mean
* Sea Level altitude is added to the {@code location}; otherwise, returns false and leaves the
* {@code location} unchanged.
+ *
+ * <p>Prior calls to {@link #addMslAltitudeToLocation(Context, Location)} off the main thread
+ * are necessary to load data from raw assets. Example code on the main thread is as follows:
+ *
+ * <pre>{@code
+ * if (!mAltitudeConverter.addMslAltitudeToLocation(location)) {
+ * // Queue up only one call off the main thread.
+ * if (mIsAltitudeConverterIdle) {
+ * mIsAltitudeConverterIdle = false;
+ * executeOffMainThread(() -> {
+ * try {
+ * // Load raw assets for next call attempt on main thread.
+ * mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
+ * } catch (IOException e) {
+ * Log.e(TAG, "Not loading raw assets: " + e);
+ * }
+ * mIsAltitudeConverterIdle = true;
+ * });
+ * }
+ * }
+ * }</pre>
*/
@FlaggedApi(Flags.FLAG_GEOID_HEIGHTS_VIA_ALTITUDE_HAL)
public boolean addMslAltitudeToLocation(@NonNull Location location) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 4918289e8b5c..69708ecde281 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -23,6 +23,7 @@ import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.media.audio.Flags.automaticBtDeviceType;
import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING;
import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
+import static android.media.audio.Flags.FLAG_SUPPORTED_DEVICE_TYPES_API;
import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.Manifest;
@@ -80,6 +81,7 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
@@ -100,6 +102,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
@@ -7838,6 +7841,51 @@ public class AudioManager {
}
/**
+ * Returns a Set of unique Integers corresponding to audio device type identifiers that can
+ * <i>potentially</i> be connected to the system and meeting the criteria specified in the
+ * <code>direction</code> parameter.
+ * Note that this set contains {@link AudioDeviceInfo} device type identifiers for both devices
+ * currently available <i>and</i> those that can be available if the user connects an audio
+ * peripheral. Examples include TYPE_WIRED_HEADSET if the Android device supports an analog
+ * headset jack or TYPE_USB_DEVICE if the Android device supports a USB host-mode port.
+ * These are generally a superset of device type identifiers associated with the
+ * AudioDeviceInfo objects returned from AudioManager.getDevices().
+ * @param direction The constant specifying whether input or output devices are queried.
+ * @see #GET_DEVICES_OUTPUTS
+ * @see #GET_DEVICES_INPUTS
+ * @return A (possibly zero-length) Set of Integer objects corresponding to the audio
+ * device types of devices supported by the implementation.
+ * @throws IllegalArgumentException If an invalid direction constant is specified.
+ */
+ @FlaggedApi(FLAG_SUPPORTED_DEVICE_TYPES_API)
+ public @NonNull Set<Integer>
+ getSupportedDeviceTypes(int direction) {
+ if (direction != GET_DEVICES_OUTPUTS && direction != GET_DEVICES_INPUTS) {
+ throw new IllegalArgumentException("AudioManager.getSupportedDeviceTypes("
+ + Integer.toHexString(direction) + ") - Invalid.");
+ }
+
+ IntArray internalDeviceTypes = new IntArray();
+ int status = AudioSystem.getSupportedDeviceTypes(direction, internalDeviceTypes);
+ if (status != AudioManager.SUCCESS) {
+ Log.e(TAG, "AudioManager.getSupportedDeviceTypes(" + direction + ") failed. status:"
+ + status);
+ }
+
+ // convert to external (AudioDeviceInfo.getType()) device IDs
+ HashSet<Integer> externalDeviceTypes = new HashSet<Integer>();
+ for (int index = 0; index < internalDeviceTypes.size(); index++) {
+ // Set will eliminate any duplicates which AudioSystem.getSupportedDeviceTypes()
+ // returns
+ externalDeviceTypes.add(
+ AudioDeviceInfo.convertInternalDeviceToDeviceType(
+ internalDeviceTypes.get(index)));
+ }
+
+ return externalDeviceTypes;
+ }
+
+ /**
* Returns an array of {@link AudioDeviceInfo} objects corresponding to the audio devices
* currently connected to the system and meeting the criteria specified in the
* <code>flags</code> parameter.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 0f6cbffef300..f73be35fa130 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -36,6 +36,7 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.Vibrator;
import android.telephony.TelephonyManager;
+import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
@@ -1947,6 +1948,8 @@ public class AudioSystem
/** @hide */
public static native int listAudioPorts(ArrayList<AudioPort> ports, int[] generation);
/** @hide */
+ public static native int getSupportedDeviceTypes(int flags, IntArray internalDeviceTypes);
+ /** @hide */
public static native int createAudioPatch(AudioPatch[] patch,
AudioPortConfig[] sources, AudioPortConfig[] sinks);
/** @hide */
diff --git a/media/java/android/media/metrics/EditingEndedEvent.java b/media/java/android/media/metrics/EditingEndedEvent.java
index 72e6db8d987f..5ed8d40af63e 100644
--- a/media/java/android/media/metrics/EditingEndedEvent.java
+++ b/media/java/android/media/metrics/EditingEndedEvent.java
@@ -86,7 +86,10 @@ public final class EditingEndedEvent extends Event implements Parcelable {
*/
public static final int ERROR_CODE_IO_NO_PERMISSION = 8;
- /** */
+ /**
+ * Caused by failing to load data via cleartext HTTP, when the app's network security
+ * configuration does not permit it.
+ */
public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 9;
/** Caused by reading data out of the data bounds. */
@@ -146,6 +149,9 @@ public final class EditingEndedEvent extends Event implements Parcelable {
@Retention(java.lang.annotation.RetentionPolicy.SOURCE)
public @interface ErrorCode {}
+ /** Special value for unknown {@linkplain #getTimeSinceCreatedMillis() time since creation}. */
+ public static final int TIME_SINCE_CREATED_UNKNOWN = -1;
+
private final @ErrorCode int mErrorCode;
@SuppressWarnings("HidingField") // Hiding field from superclass as for playback events.
private final long mTimeSinceCreatedMillis;
@@ -174,16 +180,16 @@ public final class EditingEndedEvent extends Event implements Parcelable {
}
/**
- * Gets the elapsed time since creating of the editing session, in milliseconds, or -1 if
- * unknown.
+ * Gets the elapsed time since creating of the editing session, in milliseconds, or {@link
+ * #TIME_SINCE_CREATED_UNKNOWN} if unknown.
*
- * @return The elapsed time since creating the editing session, in milliseconds, or -1 if
- * unknown.
+ * @return The elapsed time since creating the editing session, in milliseconds, or {@link
+ * #TIME_SINCE_CREATED_UNKNOWN} if unknown.
* @see LogSessionId
* @see EditingSession
*/
@Override
- @IntRange(from = -1)
+ @IntRange(from = TIME_SINCE_CREATED_UNKNOWN)
public long getTimeSinceCreatedMillis() {
return mTimeSinceCreatedMillis;
}
@@ -283,7 +289,7 @@ public final class EditingEndedEvent extends Event implements Parcelable {
public Builder(@FinalState int finalState) {
mFinalState = finalState;
mErrorCode = ERROR_CODE_NONE;
- mTimeSinceCreatedMillis = -1;
+ mTimeSinceCreatedMillis = TIME_SINCE_CREATED_UNKNOWN;
mMetricsBundle = new Bundle();
}
@@ -291,11 +297,11 @@ public final class EditingEndedEvent extends Event implements Parcelable {
* Sets the elapsed time since creating the editing session, in milliseconds.
*
* @param timeSinceCreatedMillis The elapsed time since creating the editing session, in
- * milliseconds, or -1 if the value is unknown.
+ * milliseconds, or {@link #TIME_SINCE_CREATED_UNKNOWN} if unknown.
* @see #getTimeSinceCreatedMillis()
*/
public @NonNull Builder setTimeSinceCreatedMillis(
- @IntRange(from = -1) long timeSinceCreatedMillis) {
+ @IntRange(from = TIME_SINCE_CREATED_UNKNOWN) long timeSinceCreatedMillis) {
mTimeSinceCreatedMillis = timeSinceCreatedMillis;
return this;
}
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index 897827750d4e..b6446214db31 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -69,4 +69,6 @@ oneway interface ITvInputClient {
// For ad response
void onAdResponse(in AdResponse response, int seq);
void onAdBufferConsumed(in AdBuffer buffer, int seq);
+
+ void onTvInputSessionData(in String type, in Bundle data, int seq);
}
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 2f6575e65077..84c197d9e20f 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -152,4 +152,6 @@ interface ITvInputManager {
// For freezing video playback
void setVideoFrozen(in IBinder sessionToken, boolean isFrozen, int userId);
+
+ void notifyTvAdSessionData(in IBinder sessionToken, in String type, in Bundle data, int userId);
}
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index a93f18db3dca..7b20c3976fd6 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -86,4 +86,6 @@ oneway interface ITvInputSession {
// For freezing video
void setVideoFrozen(boolean isFrozen);
+
+ void notifyTvAdSessionData(in String type, in Bundle data);
}
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index 8e2702a50662..76e079fb908c 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -68,4 +68,6 @@ oneway interface ITvInputSessionCallback {
// For messages sent from the TV input
void onTvMessage(int type, in Bundle data);
+
+ void onTvInputSessionData(in String type, in Bundle data);
}
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 921104dc70c8..999e2cfbcede 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -82,6 +82,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
private static final int DO_STOP_PLAYBACK = 33;
private static final int DO_START_PLAYBACK = 34;
private static final int DO_SET_VIDEO_FROZEN = 35;
+ private static final int DO_NOTIFY_AD_SESSION_DATA = 36;
private final boolean mIsRecordingSession;
private final HandlerCaller mCaller;
@@ -287,6 +288,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
case DO_NOTIFY_TV_MESSAGE: {
SomeArgs args = (SomeArgs) msg.obj;
mTvInputSessionImpl.onTvMessageReceived((Integer) args.arg1, (Bundle) args.arg2);
+ args.recycle();
break;
}
case DO_STOP_PLAYBACK: {
@@ -301,6 +303,12 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
mTvInputSessionImpl.setVideoFrozen((Boolean) msg.obj);
break;
}
+ case DO_NOTIFY_AD_SESSION_DATA: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mTvInputSessionImpl.notifyTvAdSessionData((String) args.arg1, (Bundle) args.arg2);
+ args.recycle();
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -488,6 +496,12 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
}
@Override
+ public void notifyTvAdSessionData(String type, Bundle data) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_NOTIFY_AD_SESSION_DATA, type, data));
+ }
+
+ @Override
public void setVideoFrozen(boolean isFrozen) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VIDEO_FROZEN, isFrozen));
}
diff --git a/media/java/android/media/tv/SignalingDataInfo.aidl b/media/java/android/media/tv/SignalingDataInfo.aidl
new file mode 100644
index 000000000000..7108f36953a9
--- /dev/null
+++ b/media/java/android/media/tv/SignalingDataInfo.aidl
@@ -0,0 +1,3 @@
+package android.media.tv;
+
+parcelable SignalingDataInfo;
diff --git a/media/java/android/media/tv/SignalingDataInfo.java b/media/java/android/media/tv/SignalingDataInfo.java
new file mode 100644
index 000000000000..b29ea5c905ec
--- /dev/null
+++ b/media/java/android/media/tv/SignalingDataInfo.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+/** @hide */
+public class SignalingDataInfo implements Parcelable {
+ public static final @NonNull Parcelable.Creator<SignalingDataInfo> CREATOR =
+ new Parcelable.Creator<SignalingDataInfo>() {
+ @Override
+ public SignalingDataInfo[] newArray(int size) {
+ return new SignalingDataInfo[size];
+ }
+
+ @Override
+ public SignalingDataInfo createFromParcel(@NonNull android.os.Parcel in) {
+ return new SignalingDataInfo(in);
+ }
+ };
+
+ private int mTableId;
+ private @NonNull String mTable;
+ private int mMetadataType;
+ private int mVersion;
+ private int mGroup;
+ private @NonNull String mEncoding;
+
+ public SignalingDataInfo(
+ int tableId,
+ @NonNull String table,
+ int metadataType,
+ int version,
+ int group,
+ @NonNull String encoding) {
+ this.mTableId = tableId;
+ this.mTable = table;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTable);
+ this.mMetadataType = metadataType;
+ this.mVersion = version;
+ this.mGroup = group;
+ this.mEncoding = encoding;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mEncoding);
+ }
+
+ public int getTableId() {
+ return mTableId;
+ }
+
+ public @NonNull String getTable() {
+ return mTable;
+ }
+
+ public int getMetadataType() {
+ return mMetadataType;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ public int getGroup() {
+ return mGroup;
+ }
+
+ public @NonNull String getEncoding() {
+ return mEncoding;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ dest.writeInt(mTableId);
+ dest.writeString(mTable);
+ dest.writeInt(mMetadataType);
+ dest.writeInt(mVersion);
+ dest.writeInt(mGroup);
+ dest.writeString(mEncoding);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ SignalingDataInfo(@NonNull android.os.Parcel in) {
+ int tableId = in.readInt();
+ String table = in.readString();
+ int metadataType = in.readInt();
+ int version = in.readInt();
+ int group = in.readInt();
+ String encoding = in.readString();
+
+ this.mTableId = tableId;
+ this.mTable = table;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTable);
+ this.mMetadataType = metadataType;
+ this.mVersion = version;
+ this.mGroup = group;
+ this.mEncoding = encoding;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mEncoding);
+ }
+}
diff --git a/media/java/android/media/tv/SignalingDataResponse.aidl b/media/java/android/media/tv/SignalingDataResponse.aidl
new file mode 100644
index 000000000000..a548e4e1edcb
--- /dev/null
+++ b/media/java/android/media/tv/SignalingDataResponse.aidl
@@ -0,0 +1,3 @@
+package android.media.tv;
+
+parcelable SignalingDataResponse;
diff --git a/media/java/android/media/tv/SignalingDataResponse.java b/media/java/android/media/tv/SignalingDataResponse.java
new file mode 100644
index 000000000000..3e4c790f6c70
--- /dev/null
+++ b/media/java/android/media/tv/SignalingDataResponse.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.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/** @hide */
+public class SignalingDataResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final @NonNull Parcelable.Creator<SignalingDataResponse> CREATOR =
+ new Parcelable.Creator<SignalingDataResponse>() {
+ @Override
+ public SignalingDataResponse[] newArray(int size) {
+ return new SignalingDataResponse[size];
+ }
+
+ @Override
+ public SignalingDataResponse createFromParcel(@NonNull android.os.Parcel in) {
+ return new SignalingDataResponse(in);
+ }
+ };
+ private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE =
+ TvInputManager.BROADCAST_INFO_TYPE_SIGNALING_DATA;
+ private final @NonNull int[] mTableIds;
+ private final int mMetadataTypes;
+ private final @NonNull List<SignalingDataInfo> mSignalingDataInfoList;
+
+ public SignalingDataResponse(
+ int requestId,
+ int sequence,
+ @ResponseResult int responseResult,
+ @NonNull int[] tableIds,
+ int metadataTypes,
+ @NonNull List<SignalingDataInfo> signalingDataInfoList) {
+ super(RESPONSE_TYPE, requestId, sequence, responseResult);
+ this.mTableIds = tableIds;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTableIds);
+ this.mMetadataTypes = metadataTypes;
+ this.mSignalingDataInfoList = signalingDataInfoList;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSignalingDataInfoList);
+ }
+
+ public @NonNull int[] getTableIds() {
+ return mTableIds;
+ }
+
+ public int getMetadataTypes() {
+ return mMetadataTypes;
+ }
+
+ public @NonNull List<SignalingDataInfo> getSignalingDataInfoList() {
+ return mSignalingDataInfoList;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeIntArray(mTableIds);
+ dest.writeInt(mMetadataTypes);
+ dest.writeParcelableList(mSignalingDataInfoList, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ SignalingDataResponse(@NonNull android.os.Parcel in) {
+ super(RESPONSE_TYPE, in);
+
+ int[] tableIds = in.createIntArray();
+ int metadataTypes = in.readInt();
+ List<SignalingDataInfo> signalingDataInfoList = new java.util.ArrayList<>();
+ in.readParcelableList(signalingDataInfoList, SignalingDataInfo.class.getClassLoader());
+
+ this.mTableIds = tableIds;
+ com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTableIds);
+ this.mMetadataTypes = metadataTypes;
+ this.mSignalingDataInfoList = signalingDataInfoList;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSignalingDataInfoList);
+ }
+}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index be1b67547103..8720bfef1e49 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -22,6 +22,7 @@ import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -535,6 +536,220 @@ public final class TvInputManager {
*/
public static final int SIGNAL_STRENGTH_STRONG = 3;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "SESSION_DATA_TYPE_", value = {
+ SESSION_DATA_TYPE_TUNED,
+ SESSION_DATA_TYPE_TRACK_SELECTED,
+ SESSION_DATA_TYPE_TRACKS_CHANGED,
+ SESSION_DATA_TYPE_VIDEO_AVAILABLE,
+ SESSION_DATA_TYPE_VIDEO_UNAVAILABLE,
+ SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE,
+ SESSION_DATA_TYPE_AD_RESPONSE,
+ SESSION_DATA_TYPE_AD_BUFFER_CONSUMED,
+ SESSION_DATA_TYPE_TV_MESSAGE})
+ public @interface SessionDataType {}
+
+ /**
+ * Informs the application that the session has been tuned to the given channel.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_CHANNEL_URI
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_TUNED = "tuned";
+
+ /**
+ * Sends the type and ID of a selected track. This is used to inform the application that a
+ * specific track is selected.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_TRACK_TYPE
+ * @see SESSION_DATA_KEY_TRACK_ID
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_TRACK_SELECTED = "track_selected";
+
+ /**
+ * Sends the list of all audio/video/subtitle tracks.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_TRACKS
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_TRACKS_CHANGED = "tracks_changed";
+
+ /**
+ * Informs the application that the video is now available for watching.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_VIDEO_AVAILABLE = "video_available";
+
+ /**
+ * Informs the application that the video became unavailable for some reason.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_VIDEO_UNAVAILABLE = "video_unavailable";
+
+ /**
+ * Notifies response for broadcast info.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE =
+ "broadcast_info_response";
+
+ /**
+ * Notifies response for advertisement.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_AD_RESPONSE
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_AD_RESPONSE = "ad_response";
+
+ /**
+ * Notifies the advertisement buffer is consumed.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_AD_BUFFER
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_AD_BUFFER_CONSUMED = "ad_buffer_consumed";
+
+ /**
+ * Sends the TV message.
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#notifyTvMessage(int, Bundle)
+ * @see SESSION_DATA_KEY_TV_MESSAGE_TYPE
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_TV_MESSAGE = "tv_message";
+
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "SESSION_DATA_KEY_", value = {
+ SESSION_DATA_KEY_CHANNEL_URI,
+ SESSION_DATA_KEY_TRACK_TYPE,
+ SESSION_DATA_KEY_TRACK_ID,
+ SESSION_DATA_KEY_TRACKS,
+ SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON,
+ SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE,
+ SESSION_DATA_KEY_AD_RESPONSE,
+ SESSION_DATA_KEY_AD_BUFFER,
+ SESSION_DATA_KEY_TV_MESSAGE_TYPE})
+ public @interface SessionDataKey {}
+
+ /**
+ * The URI of a channel.
+ *
+ * <p> Type: android.net.Uri
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_CHANNEL_URI = "channel_uri";
+
+ /**
+ * The type of the track.
+ *
+ * <p>One of {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO},
+ * {@link TvTrackInfo#TYPE_SUBTITLE}.
+ *
+ * <p> Type: Integer
+ *
+ * @see TvTrackInfo#getType()
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_TRACK_TYPE = "track_type";
+
+ /**
+ * The ID of the track.
+ *
+ * <p> Type: String
+ *
+ * @see TvTrackInfo#getId()
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_TRACK_ID = "track_id";
+
+ /**
+ * A list which includes track information.
+ *
+ * <p> Type: {@code java.util.List<android.media.tv.TvTrackInfo> }
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_TRACKS = "tracks";
+
+ /**
+ * The reason why the video became unavailable.
+ * <p>The value can be {@link VIDEO_UNAVAILABLE_REASON_BUFFERING},
+ * {@link VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}, etc.
+ *
+ * <p> Type: Integer
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON =
+ "video_unavailable_reason";
+
+ /**
+ * An object of {@link BroadcastInfoResponse}.
+ *
+ * <p> Type: android.media.tv.BroadcastInfoResponse
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
+
+ /**
+ * An object of {@link AdResponse}.
+ *
+ * <p> Type: android.media.tv.AdResponse
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_AD_RESPONSE = "ad_response";
+
+ /**
+ * An object of {@link AdBuffer}.
+ *
+ * <p> Type: android.media.tv.AdBuffer
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
+
+ /**
+ * The type of TV message.
+ * <p>It can be one of {@link TV_MESSAGE_TYPE_WATERMARK},
+ * {@link TV_MESSAGE_TYPE_CLOSED_CAPTION}, {@link TV_MESSAGE_TYPE_OTHER}
+ *
+ * <p> Type: Integer
+ *
+ * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_TV_MESSAGE_TYPE = "tv_message_type";
+
+
/**
* An unknown state of the client pid gets from the TvInputManager. Client gets this value when
* query through {@link getClientPid(String sessionId)} fails.
@@ -1271,6 +1486,17 @@ public final class TvInputManager {
});
}
}
+
+ void postTvInputSessionData(String type, Bundle data) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mSession.getAdSession() != null) {
+ mSession.getAdSession().notifyTvInputSessionData(type, data);
+ }
+ }
+ });
+ }
}
/**
@@ -1820,6 +2046,18 @@ public final class TvInputManager {
record.postAdBufferConsumed(buffer);
}
}
+
+ @Override
+ public void onTvInputSessionData(String type, Bundle data, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postTvInputSessionData(type, data);
+ }
+ }
};
ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() {
@Override
@@ -3844,6 +4082,21 @@ public final class TvInputManager {
}
}
+ /**
+ * Notifies data from session of linked TvAdService.
+ */
+ public void notifyTvAdSessionData(String type, Bundle data) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyTvAdSessionData(mToken, type, data, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private final class InputEventHandler extends Handler {
public static final int MSG_SEND_INPUT_EVENT = 1;
public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 6301a27bac4d..a022b1c7cf64 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -34,6 +34,7 @@ import android.graphics.Rect;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioPresentation;
import android.media.PlaybackParams;
+import android.media.tv.ad.TvAdManager;
import android.media.tv.interactive.TvInteractiveAppService;
import android.net.Uri;
import android.os.AsyncTask;
@@ -1259,6 +1260,37 @@ public abstract class TvInputService extends Service {
}
/**
+ * Notifies data related to this session to corresponding linked
+ * {@link android.media.tv.ad.TvAdService} object via TvAdView.
+ *
+ * <p>Methods like {@link #notifyBroadcastInfoResponse(BroadcastInfoResponse)} sends the
+ * related data to linked {@link android.media.tv.interactive.TvInteractiveAppService}, but
+ * don't work for {@link android.media.tv.ad.TvAdService}. The method is used specifically
+ * for {@link android.media.tv.ad.TvAdService} use cases.
+ *
+ * @param type data type
+ * @param data the related data values
+ * @hide
+ */
+ public void notifyTvInputSessionData(
+ @NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "notifyTvInputSessionData");
+ if (mSessionCallback != null) {
+ mSessionCallback.onTvInputSessionData(type, data);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in notifyTvInputSessionData", e);
+ }
+ }
+ });
+ }
+
+ /**
* Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
* is relative to the overlay view that sits on top of this surface.
*
@@ -1401,6 +1433,20 @@ public abstract class TvInputService extends Service {
public void onAdBufferReady(@NonNull AdBuffer buffer) {
}
+
+ /**
+ * Called when data from the linked {@link android.media.tv.ad.TvAdService} is received.
+ *
+ * @param type the type of the data
+ * @param data a bundle contains the data received
+ * @see android.media.tv.ad.TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see android.media.tv.ad.TvAdView#setTvView(TvView)
+ * @hide
+ */
+ public void onTvAdSessionData(
+ @NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
+ }
+
/**
* Tunes to a given channel.
*
@@ -2166,6 +2212,10 @@ public abstract class TvInputService extends Service {
onAdBufferReady(buffer);
}
+ void notifyTvAdSessionData(String type, Bundle data) {
+ onTvAdSessionData(type, data);
+ }
+
void onTvMessageReceived(int type, Bundle data) {
onTvMessage(type, data);
}
diff --git a/media/java/android/media/tv/ad/ITvAdClient.aidl b/media/java/android/media/tv/ad/ITvAdClient.aidl
index 34d96b374a35..49046d029198 100644
--- a/media/java/android/media/tv/ad/ITvAdClient.aidl
+++ b/media/java/android/media/tv/ad/ITvAdClient.aidl
@@ -16,6 +16,7 @@
package android.media.tv.ad;
+import android.os.Bundle;
import android.view.InputChannel;
/**
@@ -27,4 +28,11 @@ oneway interface ITvAdClient {
void onSessionCreated(in String serviceId, IBinder token, in InputChannel channel, int seq);
void onSessionReleased(int seq);
void onLayoutSurface(int left, int top, int right, int bottom, int seq);
+ void onRequestCurrentVideoBounds(int seq);
+ void onRequestCurrentChannelUri(int seq);
+ void onRequestTrackInfoList(int seq);
+ void onRequestCurrentTvInputId(int seq);
+ void onRequestSigning(
+ in String id, in String algorithm, in String alias, in byte[] data, int seq);
+ void onTvAdSessionData(in String type, in Bundle data, int seq);
} \ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
index 9620065ecf33..5c5f095fddcd 100644
--- a/media/java/android/media/tv/ad/ITvAdManager.aidl
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -31,6 +31,7 @@ import android.view.Surface;
*/
interface ITvAdManager {
List<TvAdServiceInfo> getTvAdServiceList(int userId);
+ void sendAppLinkCommand(String serviceId, in Bundle command, int userId);
void createSession(
in ITvAdClient client, in String serviceId, in String type, int seq, int userId);
void releaseSession(in IBinder sessionToken, int userId);
@@ -58,4 +59,7 @@ interface ITvAdManager {
int userId);
void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId);
void removeMediaView(in IBinder sessionToken, int userId);
+
+ void notifyTvInputSessionData(
+ in IBinder sessionToken, in String type, in Bundle data, int userId);
}
diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl
index 69afb175bb83..eba0bc83f3f2 100644
--- a/media/java/android/media/tv/ad/ITvAdSession.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -46,4 +46,6 @@ oneway interface ITvAdSession {
void createMediaView(in IBinder windowToken, in Rect frame);
void relayoutMediaView(in Rect frame);
void removeMediaView();
+
+ void notifyTvInputSessionData(in String type, in Bundle data);
}
diff --git a/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
index f21ef198ab01..e4e5cbb6c251 100644
--- a/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
@@ -17,6 +17,7 @@
package android.media.tv.ad;
import android.media.tv.ad.ITvAdSession;
+import android.os.Bundle;
/**
* Helper interface for ITvAdSession to allow TvAdService to notify the system service when there is
@@ -26,4 +27,10 @@ import android.media.tv.ad.ITvAdSession;
oneway interface ITvAdSessionCallback {
void onSessionCreated(in ITvAdSession session);
void onLayoutSurface(int left, int top, int right, int bottom);
+ void onRequestCurrentVideoBounds();
+ void onRequestCurrentChannelUri();
+ void onRequestTrackInfoList();
+ void onRequestCurrentTvInputId();
+ void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data);
+ void onTvAdSessionData(in String type, in Bundle data);
} \ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
index 251351de609e..e10a20e042e6 100644
--- a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
+++ b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
@@ -65,6 +65,7 @@ public class ITvAdSessionWrapper
private static final int DO_SEND_SIGNING_RESULT = 14;
private static final int DO_NOTIFY_ERROR = 15;
private static final int DO_NOTIFY_TV_MESSAGE = 16;
+ private static final int DO_NOTIFY_INPUT_SESSION_DATA = 17;
private final HandlerCaller mCaller;
private TvAdService.Session mSessionImpl;
@@ -180,6 +181,12 @@ public class ITvAdSessionWrapper
args.recycle();
break;
}
+ case DO_NOTIFY_INPUT_SESSION_DATA: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.notifyTvInputSessionData((String) args.arg1, (Bundle) args.arg2);
+ args.recycle();
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -280,6 +287,12 @@ public class ITvAdSessionWrapper
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_MEDIA_VIEW));
}
+ @Override
+ public void notifyTvInputSessionData(String type, Bundle data) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_NOTIFY_INPUT_SESSION_DATA, type, data));
+ }
+
private final class TvAdEventReceiver extends InputEventReceiver {
TvAdEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 4dce72f62785..02c0d75dc2b6 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -16,12 +16,15 @@
package android.media.tv.ad;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringDef;
import android.annotation.SystemService;
import android.content.Context;
import android.graphics.Rect;
+import android.media.tv.AdBuffer;
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.media.tv.flags.Flags;
@@ -43,7 +46,10 @@ import android.view.View;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executor;
@@ -57,6 +63,198 @@ public class TvAdManager {
// TODO: implement more methods and unhide APIs.
private static final String TAG = "TvAdManager";
+ /**
+ * Key for package name in app link.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
+
+ /**
+ * Key for class name in app link.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
+
+ /**
+ * Key for command type in app link command.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
+
+ /**
+ * Key for service ID in app link command.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
+
+ /**
+ * Key for back URI in app link command.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String APP_LINK_KEY_BACK_URI = "back_uri";
+
+ /**
+ * Broadcast intent action to send app command to TV app.
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @hide
+ */
+ public static final String ACTION_APP_LINK_COMMAND =
+ "android.media.tv.ad.action.APP_LINK_COMMAND";
+
+ /**
+ * Intent key for TV input ID. It's used to send app command to TV app.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @see #ACTION_APP_LINK_COMMAND
+ * @hide
+ */
+ public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
+
+ /**
+ * Intent key for TV AD service ID. It's used to send app command to TV app.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @see #ACTION_APP_LINK_COMMAND
+ * @see TvAdServiceInfo#getId()
+ * @hide
+ */
+ public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
+
+ /**
+ * Intent key for TV channel URI. It's used to send app command to TV app.
+ * <p>Type: android.net.Uri
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @see #ACTION_APP_LINK_COMMAND
+ * @hide
+ */
+ public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
+
+ /**
+ * Intent key for command type. It's used to send app command to TV app. The value of this key
+ * could vary according to TV apps.
+ * <p>Type: String
+ *
+ * @see #sendAppLinkCommand(String, Bundle)
+ * @see #ACTION_APP_LINK_COMMAND
+ * @hide
+ */
+ public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "SESSION_DATA_TYPE_", value = {
+ SESSION_DATA_TYPE_AD_REQUEST,
+ SESSION_DATA_TYPE_AD_BUFFER_READY,
+ SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST,
+ SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST})
+ public @interface SessionDataType {}
+
+ /**
+ * Sends an advertisement request to be processed by the related TV input.
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_AD_REQUEST
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_AD_REQUEST = "ad_request";
+
+ /**
+ * Notifies the advertisement buffer is ready.
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_AD_BUFFER
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_AD_BUFFER_READY = "ad_buffer_ready";
+
+ /**
+ * Sends request for broadcast info.
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_BROADCAST_INFO_RESQUEST
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST = "broadcast_info_request";
+
+ /**
+ * Removes request for broadcast info.
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see SESSION_DATA_KEY_BROADCAST_INFO_REQUEST_ID
+ * @hide
+ */
+ public static final String SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST =
+ "remove_broadcast_info_request";
+
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "SESSION_DATA_KEY_", value = {
+ SESSION_DATA_KEY_AD_REQUEST,
+ SESSION_DATA_KEY_AD_BUFFER,
+ SESSION_DATA_KEY_BROADCAST_INFO_REQUEST,
+ SESSION_DATA_KEY_REQUEST_ID})
+ public @interface SessionDataKey {}
+
+ /**
+ * An object of {@link android.media.tv.AdRequest}.
+ *
+ * <p> Type: android.media.tv.AdRequest
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_AD_REQUEST = "ad_request";
+
+ /**
+ * An object of {@link AdBuffer}.
+ *
+ * <p> Type: android.media.tv.AdBuffer
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
+
+ /**
+ * An object of {@link android.media.tv.BroadcastInfoRequest}.
+ *
+ * <p> Type: android.media.tv.BroadcastInfoRequest
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_BROADCAST_INFO_REQUEST = "broadcast_info_request";
+
+ /**
+ * The ID of {@link android.media.tv.BroadcastInfoRequest}.
+ *
+ * <p> Type: Integer
+ *
+ * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @hide
+ */
+ public static final String SESSION_DATA_KEY_REQUEST_ID = "request_id";
+
private final ITvAdManager mService;
private final int mUserId;
@@ -125,6 +323,79 @@ public class TvAdManager {
}
}
+ @Override
+ public void onRequestCurrentVideoBounds(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestCurrentVideoBounds();
+ }
+ }
+
+ @Override
+ public void onRequestCurrentChannelUri(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestCurrentChannelUri();
+ }
+ }
+
+ @Override
+ public void onRequestTrackInfoList(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestTrackInfoList();
+ }
+ }
+
+ @Override
+ public void onRequestCurrentTvInputId(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestCurrentTvInputId();
+ }
+ }
+
+ @Override
+ public void onRequestSigning(
+ String id, String algorithm, String alias, byte[] data, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestSigning(id, algorithm, alias, data);
+ }
+ }
+
+ @Override
+ public void onTvAdSessionData(String type, Bundle data, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postTvAdSessionData(type, data);
+ }
+ }
+
};
ITvAdManagerCallback managerCallback =
@@ -220,6 +491,59 @@ public class TvAdManager {
}
/**
+ * Sends app link command.
+ *
+ * @param serviceId The ID of TV AD service which the command to be sent to. The ID can be found
+ * in {@link TvAdServiceInfo#getId()}.
+ * @param command The command to be sent.
+ * @hide
+ */
+ public void sendAppLinkCommand(@NonNull String serviceId, @NonNull Bundle command) {
+ try {
+ mService.sendAppLinkCommand(serviceId, command, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a {@link TvAdServiceCallback}.
+ *
+ * @param callback A callback used to monitor status of the TV AD services.
+ * @param executor A {@link Executor} that the status change will be delivered to.
+ * @hide
+ */
+ public void registerCallback(
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull TvAdServiceCallback callback) {
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(executor);
+ synchronized (mLock) {
+ mCallbackRecords.add(new TvAdServiceCallbackRecord(callback, executor));
+ }
+ }
+
+ /**
+ * Unregisters the existing {@link TvAdServiceCallback}.
+ *
+ * @param callback The existing callback to remove.
+ * @hide
+ */
+ public void unregisterCallback(@NonNull final TvAdServiceCallback callback) {
+ Preconditions.checkNotNull(callback);
+ synchronized (mLock) {
+ for (Iterator<TvAdServiceCallbackRecord> it = mCallbackRecords.iterator();
+ it.hasNext(); ) {
+ TvAdServiceCallbackRecord record = it.next();
+ if (record.getCallback() == callback) {
+ it.remove();
+ break;
+ }
+ }
+ }
+ }
+
+ /**
* The Session provides the per-session functionality of AD service.
* @hide
*/
@@ -533,6 +857,88 @@ public class TvAdManager {
}
}
+ /**
+ * Notifies data from session of linked TvInputService.
+ */
+ public void notifyTvInputSessionData(String type, Bundle data) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyTvInputSessionData(mToken, type, data, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Dispatches an input event to this session.
+ *
+ * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}.
+ * @param token A token used to identify the input event later in the callback.
+ * @param callback A callback used to receive the dispatch result. Cannot be {@code null}.
+ * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be
+ * {@code null}.
+ * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
+ * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
+ * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
+ * be invoked later.
+ * @hide
+ */
+ public int dispatchInputEvent(@NonNull InputEvent event, Object token,
+ @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) {
+ Preconditions.checkNotNull(event);
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(handler);
+ synchronized (mHandler) {
+ if (mInputChannel == null) {
+ return DISPATCH_NOT_HANDLED;
+ }
+ PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ // Already running on the main thread so we can send the event immediately.
+ return sendInputEventOnMainLooperLocked(p);
+ }
+
+ // Post the event to the main thread.
+ Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ return DISPATCH_IN_PROGRESS;
+ }
+ }
+
+ private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
+ FinishedInputEventCallback callback, Handler handler) {
+ PendingEvent p = mPendingEventPool.acquire();
+ if (p == null) {
+ p = new PendingEvent();
+ }
+ p.mEvent = event;
+ p.mEventToken = token;
+ p.mCallback = callback;
+ p.mEventHandler = handler;
+ return p;
+ }
+
+ /**
+ * Callback that is invoked when an input event that was dispatched to this session has been
+ * finished.
+ *
+ * @hide
+ */
+ public interface FinishedInputEventCallback {
+ /**
+ * Called when the dispatched input event is finished.
+ *
+ * @param token A token passed to {@link #dispatchInputEvent}.
+ * @param handled {@code true} if the dispatched input event was handled properly.
+ * {@code false} otherwise.
+ */
+ void onFinishedInputEvent(Object token, boolean handled);
+ }
+
private final class InputEventHandler extends Handler {
public static final int MSG_SEND_INPUT_EVENT = 1;
public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
@@ -639,23 +1045,6 @@ public class TvAdManager {
mPendingEventPool.release(p);
}
- /**
- * Callback that is invoked when an input event that was dispatched to this session has been
- * finished.
- *
- * @hide
- */
- public interface FinishedInputEventCallback {
- /**
- * Called when the dispatched input event is finished.
- *
- * @param token A token passed to {@link #dispatchInputEvent}.
- * @param handled {@code true} if the dispatched input event was handled properly.
- * {@code false} otherwise.
- */
- void onFinishedInputEvent(Object token, boolean handled);
- }
-
private final class TvInputEventSender extends InputEventSender {
TvInputEventSender(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
@@ -730,6 +1119,58 @@ public class TvAdManager {
public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
}
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentVideoBounds} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ */
+ public void onRequestCurrentVideoBounds(Session session) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentChannelUri} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ */
+ public void onRequestCurrentChannelUri(Session session) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestTrackInfoList} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ */
+ public void onRequestTrackInfoList(Session session) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentTvInputId} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ */
+ public void onRequestCurrentTvInputId(Session session) {
+ }
+
+ /**
+ * This is called when
+ * {@link TvAdService.Session#requestSigning(String, String, String, byte[])} is
+ * called.
+ *
+ * @param session A {@link TvAdService.Session} associated with this callback.
+ * @param signingId the ID to identify the request.
+ * @param algorithm the standard name of the signature algorithm requested, such as
+ * MD5withRSA, SHA256withDSA, etc.
+ * @param alias the alias of the corresponding {@link java.security.KeyStore}.
+ * @param data the original bytes to be signed.
+ */
+ public void onRequestSigning(
+ Session session, String signingId, String algorithm, String alias, byte[] data) {
+ }
+
}
/**
@@ -810,6 +1251,62 @@ public class TvAdManager {
}
});
}
+
+ void postRequestCurrentVideoBounds() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestCurrentVideoBounds(mSession);
+ }
+ });
+ }
+
+ void postRequestCurrentChannelUri() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestCurrentChannelUri(mSession);
+ }
+ });
+ }
+
+ void postRequestTrackInfoList() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestTrackInfoList(mSession);
+ }
+ });
+ }
+
+ void postRequestCurrentTvInputId() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestCurrentTvInputId(mSession);
+ }
+ });
+ }
+
+ void postRequestSigning(String id, String algorithm, String alias, byte[] data) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestSigning(mSession, id, algorithm, alias, data);
+ }
+ });
+ }
+
+ void postTvAdSessionData(String type, Bundle data) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mSession.getInputSession() != null) {
+ mSession.getInputSession().notifyTvAdSessionData(type, data);
+ }
+ }
+ });
+ }
}
private static final class TvAdServiceCallbackRecord {
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 5d818375ad53..4d8f5c8b640b 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -31,6 +31,7 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
+import android.media.tv.TvView;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -132,6 +133,9 @@ public abstract class TvAdService extends Service {
/**
* Called when app link command is received.
+ *
+ * @see TvAdManager#sendAppLinkCommand(String, Bundle)
+ * @hide
*/
public void onAppLinkCommand(@NonNull Bundle command) {
}
@@ -279,6 +283,143 @@ public abstract class TvAdService extends Service {
onResetAdService();
}
+ /**
+ * Requests the bounds of the current video.
+ * @hide
+ */
+ @CallSuper
+ public void requestCurrentVideoBounds() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestCurrentVideoBounds");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestCurrentVideoBounds();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestCurrentVideoBounds", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Requests the URI of the current channel.
+ * @hide
+ */
+ @CallSuper
+ public void requestCurrentChannelUri() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestCurrentChannelUri");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestCurrentChannelUri();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestCurrentChannelUri", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Requests the list of {@link TvTrackInfo}.
+ * @hide
+ */
+ @CallSuper
+ public void requestTrackInfoList() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestTrackInfoList");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestTrackInfoList();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestTrackInfoList", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Requests current TV input ID.
+ *
+ * @see android.media.tv.TvInputInfo
+ * @hide
+ */
+ @CallSuper
+ public void requestCurrentTvInputId() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestCurrentTvInputId");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestCurrentTvInputId();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestCurrentTvInputId", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Requests signing of the given data.
+ *
+ * <p>This is used when the corresponding server of the AD service app requires signing
+ * during handshaking, and the service doesn't have the built-in private key. The private
+ * key is provided by the content providers and pre-built in the related app, such as TV
+ * app.
+ *
+ * @param signingId the ID to identify the request. When a result is received, this ID can
+ * be used to correlate the result with the request.
+ * @param algorithm the standard name of the signature algorithm requested, such as
+ * MD5withRSA, SHA256withDSA, etc. The name is from standards like
+ * FIPS PUB 186-4 and PKCS #1.
+ * @param alias the alias of the corresponding {@link java.security.KeyStore}.
+ * @param data the original bytes to be signed.
+ *
+ * @see #onSigningResult(String, byte[])
+ */
+ @CallSuper
+ public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
+ @NonNull String alias, @NonNull byte[] data) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestSigning");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestSigning(signingId, algorithm, alias, data);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestSigning", e);
+ }
+ }
+ });
+ }
+
@Override
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
return false;
@@ -470,6 +611,19 @@ public abstract class TvAdService extends Service {
}
/**
+ * Called when data from the linked {@link android.media.tv.TvInputService} is received.
+ *
+ * @param type the type of the data
+ * @param data a bundle contains the data received
+ * @see android.media.tv.TvInputService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see android.media.tv.ad.TvAdView#setTvView(TvView)
+ * @hide
+ */
+ public void onTvInputSessionData(
+ @NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
+ }
+
+ /**
* Called when the size of the media view is changed by the application.
*
* <p>This is always called at least once when the session is created regardless of whether
@@ -497,6 +651,33 @@ public abstract class TvAdService extends Service {
}
/**
+ * Notifies data related to this session to corresponding linked
+ * {@link android.media.tv.TvInputService} object via TvView.
+ *
+ * @param type data type
+ * @param data the related data values
+ * @see TvAdView#setTvView(TvView)
+ * @hide
+ */
+ public void notifyTvAdSessionData(
+ @NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "notifyTvAdSessionData");
+ if (mSessionCallback != null) {
+ mSessionCallback.onTvAdSessionData(type, data);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in notifyTvAdSessionData", e);
+ }
+ }
+ });
+ }
+
+ /**
* Takes care of dispatching incoming input events and tells whether the event was handled.
*/
int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
@@ -594,6 +775,10 @@ public abstract class TvAdService extends Service {
onTvMessage(type, data);
}
+ void notifyTvInputSessionData(String type, Bundle data) {
+ onTvInputSessionData(type, data);
+ }
+
private void executeOrPostRunnableOnMainThread(Runnable action) {
synchronized (mLock) {
if (mSessionCallback == null) {
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index ec23b7c4532d..604dbd5a3399 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -28,17 +28,21 @@ import android.graphics.RectF;
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
+import android.media.tv.ad.TvAdManager.Session.FinishedInputEventCallback;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
+import android.view.InputEvent;
+import android.view.KeyEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewRootImpl;
import java.util.List;
import java.util.concurrent.Executor;
@@ -88,6 +92,7 @@ public class TvAdView extends ViewGroup {
private boolean mMediaViewCreated;
private Rect mMediaViewFrame;
+ private OnUnhandledInputEventListener mOnUnhandledInputEventListener;
@@ -327,6 +332,109 @@ public class TvAdView extends ViewGroup {
mSession.dispatchSurfaceChanged(format, width, height);
}
+ private final FinishedInputEventCallback mFinishedInputEventCallback =
+ new FinishedInputEventCallback() {
+ @Override
+ public void onFinishedInputEvent(Object token, boolean handled) {
+ if (DEBUG) {
+ Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled="
+ + handled + ")");
+ }
+ if (handled) {
+ return;
+ }
+ // TODO: Re-order unhandled events.
+ InputEvent event = (InputEvent) token;
+ if (dispatchUnhandledInputEvent(event)) {
+ return;
+ }
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.dispatchUnhandledInputEvent(event);
+ }
+ }
+ };
+
+ /**
+ * Dispatches an unhandled input event to the next receiver.
+ *
+ * It gives the host application a chance to dispatch the unhandled input events.
+ *
+ * @param event The input event.
+ * @return {@code true} if the event was handled by the view, {@code false} otherwise.
+ * @hide
+ */
+ public boolean dispatchUnhandledInputEvent(@NonNull InputEvent event) {
+ if (mOnUnhandledInputEventListener != null) {
+ if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) {
+ return true;
+ }
+ }
+ return onUnhandledInputEvent(event);
+ }
+
+ /**
+ * Called when an unhandled input event also has not been handled by the user provided
+ * callback. This is the last chance to handle the unhandled input event in the
+ * TvAdView.
+ *
+ * @param event The input event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to be
+ * handled by the next receiver, return {@code false}.
+ */
+ public boolean onUnhandledInputEvent(@NonNull InputEvent event) {
+ return false;
+ }
+
+ /**
+ * Sets a listener to be invoked when an input event is not handled
+ * by the TV AD service.
+ *
+ * @param listener The callback to be invoked when the unhandled input event is received.
+ * @hide
+ */
+ public void setOnUnhandledInputEventListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnUnhandledInputEventListener listener) {
+ mOnUnhandledInputEventListener = listener;
+ // TODO: handle CallbackExecutor
+ }
+
+ /**
+ * Gets the {@link OnUnhandledInputEventListener}.
+ * <p>Returns {@code null} if the listener is not set or is cleared.
+ *
+ * @see #setOnUnhandledInputEventListener(Executor, OnUnhandledInputEventListener)
+ * @see #clearOnUnhandledInputEventListener()
+ * @hide
+ */
+ @Nullable
+ public OnUnhandledInputEventListener getOnUnhandledInputEventListener() {
+ return mOnUnhandledInputEventListener;
+ }
+
+ /**
+ * Clears the {@link OnUnhandledInputEventListener}.
+ * @hide
+ */
+ public void clearOnUnhandledInputEventListener() {
+ mOnUnhandledInputEventListener = null;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
+ if (super.dispatchKeyEvent(event)) {
+ return true;
+ }
+ if (mSession == null) {
+ return false;
+ }
+ InputEvent copiedEvent = event.copy();
+ int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
+ mHandler);
+ return ret != TvAdManager.Session.DISPATCH_NOT_HANDLED;
+ }
+
/**
* Prepares the AD service of corresponding {@link TvAdService}.
*
@@ -504,6 +612,24 @@ public class TvAdView extends ViewGroup {
}
/**
+ * Interface definition for a callback to be invoked when the unhandled input event is received.
+ * @hide
+ */
+ public interface OnUnhandledInputEventListener {
+ /**
+ * Called when an input event was not handled by the TV AD service.
+ *
+ * <p>This is called asynchronously from where the event is dispatched. It gives the host
+ * application a chance to handle the unhandled input events.
+ *
+ * @param event The input event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ boolean onUnhandledInputEvent(@NonNull InputEvent event);
+ }
+
+ /**
* Sets the callback to be invoked when an event is dispatched to this TvAdView.
*
* @param callback the callback to receive events. MUST NOT be {@code null}.
@@ -606,6 +732,117 @@ public class TvAdView extends ViewGroup {
mUseRequestedSurfaceLayout = true;
requestLayout();
}
+
+ @Override
+ public void onRequestCurrentVideoBounds(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestCurrentVideoBounds");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestCurrentVideoBounds - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestCurrentVideoBounds(mServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCurrentChannelUri(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestCurrentChannelUri");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestCurrentChannelUri - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestCurrentChannelUri(mServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onRequestTrackInfoList(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestTrackInfoList");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestTrackInfoList - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestTrackInfoList(mServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCurrentTvInputId(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestCurrentTvInputId");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestCurrentTvInputId - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestCurrentTvInputId(mServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onRequestSigning(TvAdManager.Session session, String id, String algorithm,
+ String alias, byte[] data) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestSigning");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestSigning - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestSigning(mServiceId, id, algorithm, alias, data);
+ }
+ }
+ });
+ }
+ }
+ }
}
/**
@@ -613,5 +850,55 @@ public class TvAdView extends ViewGroup {
* @hide
*/
public abstract static class TvAdCallback {
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentVideoBounds()}
+ * is called.
+ *
+ * @param serviceId The ID of the TV AD service bound to this view.
+ */
+ public void onRequestCurrentVideoBounds(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentChannelUri()} is
+ * called.
+ *
+ * @param serviceId The ID of the AD service bound to this view.
+ */
+ public void onRequestCurrentChannelUri(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestTrackInfoList()} is called.
+ *
+ * @param serviceId The ID of the AD service bound to this view.
+ */
+ public void onRequestTrackInfoList(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#requestCurrentTvInputId()} is called.
+ *
+ * @param serviceId The ID of the AD service bound to this view.
+ */
+ public void onRequestCurrentTvInputId(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when
+ * {@link TvAdService.Session#requestSigning(String, String, String, byte[])} is called.
+ *
+ * @param serviceId The ID of the AD service bound to this view.
+ * @param signingId the ID to identify the request.
+ * @param algorithm the standard name of the signature algorithm requested, such as
+ * MD5withRSA, SHA256withDSA, etc.
+ * @param alias the alias of the corresponding {@link java.security.KeyStore}.
+ * @param data the original bytes to be signed.
+ *
+ */
+ public void onRequestSigning(@NonNull String serviceId, @NonNull String signingId,
+ @NonNull String algorithm, @NonNull String alias, @NonNull byte[] data) {
+ }
}
}
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 018eaf6dc32f..f1107059111c 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -12,4 +12,11 @@ flag {
namespace: "media_tv"
description: "Enable the TV client-side AD framework."
bug: "303506816"
+}
+
+flag {
+ name: "tiaf_v_apis"
+ namespace: "media_tv"
+ description: "TIAF V3.0 APIs for Android V"
+ bug: "303323657"
} \ No newline at end of file
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 3524f8cce04c..f8b640c7d5b3 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -17,7 +17,7 @@ package android.nfc {
method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
- method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderModePollingEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setWlcEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 11eb97b440bf..4d56c11d1c39 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1764,7 +1764,9 @@ public final class NfcAdapter {
private static final int ENABLE_POLLING_FLAGS = 0x0000;
/**
- * Privileged API to enable disable reader polling.
+ * Privileged API to enable or disable reader polling.
+ * Unlike {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}, this API does not
+ * need a foreground activity to control reader mode parameters
* Note: Use with caution! The app is responsible for ensuring that the polling state is
* returned to normal.
*
@@ -1778,14 +1780,14 @@ public final class NfcAdapter {
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
@SuppressLint("VisiblySynchronized")
- public void setReaderMode(boolean enablePolling) {
+ public void setReaderModePollingEnabled(boolean enable) {
synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
}
Binder token = new Binder();
- int flags = enablePolling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
+ int flags = enable ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
try {
NfcAdapter.sService.setReaderMode(token, null, flags, null);
} catch (RemoteException e) {
diff --git a/packages/CredentialManager/res/drawable/more_horiz_24px.xml b/packages/CredentialManager/res/drawable/more_horiz_24px.xml
index 7b235f84f0fa..0100718ffd08 100644
--- a/packages/CredentialManager/res/drawable/more_horiz_24px.xml
+++ b/packages/CredentialManager/res/drawable/more_horiz_24px.xml
@@ -3,6 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
+ android:contentDescription="@string/more_options_content_description"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
index 929756cdf9cc..fdda9ea06ab9 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -17,6 +17,7 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:minWidth="@dimen/dropdown_touch_target_min_width"
android:orientation="horizontal"
android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
@@ -27,7 +28,7 @@
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
- android:contentDescription="@string/provider_icon_content_description"
+ android:contentDescription="@string/more_options_content_description"
android:background="@null"/>
<TextView
android:id="@android:id/text1"
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index 1fe5e0ed41f9..c7c2fda6a489 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -17,6 +17,7 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:minWidth="@dimen/dropdown_touch_target_min_width"
android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
@@ -24,7 +25,6 @@
android:id="@android:id/icon1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:contentDescription="@string/provider_icon_content_description"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:background="@null"/>
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 3a8c78f6d854..53852cbd0d10 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -27,4 +27,5 @@
<dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
<dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
<integer name="autofill_max_visible_datasets">3</integer>
+ <dimen name="dropdown_touch_target_min_width">48dp</dimen>
</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index f98164b8788c..82b47a964204 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -172,5 +172,5 @@
<!-- Strings for dropdown presentation. -->
<!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] -->
<string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string>
- <string name="provider_icon_content_description">Credential provider icon</string>
+ <string name="more_options_content_description">More</string>
</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index 68f1c861d51b..02afc547e3f8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -74,6 +74,8 @@ class RemoteViewsFactory {
setMaxHeightMethodName,
context.resources.getDimensionPixelSize(
com.android.credentialmanager.R.dimen.autofill_icon_size));
+ remoteViews.setContentDescription(android.R.id.icon1, credentialEntryInfo
+ .providerDisplayName);
val drawableId =
com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
remoteViews.setInt(
diff --git a/packages/CtsShim/OWNERS b/packages/CtsShim/OWNERS
index 94197715150d..f5741a01e68c 100644
--- a/packages/CtsShim/OWNERS
+++ b/packages/CtsShim/OWNERS
@@ -1,3 +1,2 @@
-ioffe@google.com
-toddke@google.com
-patb@google.com \ No newline at end of file
+include /PACKAGE_MANAGER_OWNERS
+ioffe@google.com \ No newline at end of file
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 5a21d59c6471..09e0d61010f7 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -75,19 +75,15 @@
</intent-filter>
</activity>
- <!-- NOTE: the workaround to fix the screen flash problem. Remember to check the problem
- is resolved for new implementation -->
<activity android:name=".InstallStaging"
- android:theme="@style/Theme.AlertDialogActivity.NoDim"
- android:exported="false" />
+ android:exported="false" />
<activity android:name=".DeleteStagedFileOnResult"
android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
android:exported="false" />
<activity android:name=".PackageInstallerActivity"
- android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
- android:exported="false" />
+ android:exported="false" />
<activity android:name=".InstallInstalling"
android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
diff --git a/packages/PackageInstaller/OWNERS b/packages/PackageInstaller/OWNERS
index 27368705c6b0..acbdff889f56 100644
--- a/packages/PackageInstaller/OWNERS
+++ b/packages/PackageInstaller/OWNERS
@@ -1,5 +1,4 @@
-svetoslavganov@google.com
+# Bug component: 1002434
include /PACKAGE_MANAGER_OWNERS
-# For automotive related changes
-rogerxue@google.com
+sumedhsen@google.com \ No newline at end of file
diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml
index 811fa7380c48..9a062295608d 100644
--- a/packages/PackageInstaller/res/values/themes.xml
+++ b/packages/PackageInstaller/res/values/themes.xml
@@ -32,9 +32,4 @@
<item name="android:windowNoTitle">true</item>
</style>
- <style name="Theme.AlertDialogActivity.NoDim"
- parent="@style/Theme.AlertDialogActivity.NoActionBar">
- <item name="android:backgroundDimAmount">0</item>
- </style>
-
</resources>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index ceb580d170d0..7dc157fbb523 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -308,7 +308,6 @@ public class PackageInstallerActivity extends Activity {
}
private void initiateInstall() {
- bindUi();
String pkgName = mPkgInfo.packageName;
// Check if there is already a package on the device with this name
// but it has been renamed to something else.
@@ -448,6 +447,7 @@ public class PackageInstallerActivity extends Activity {
if (mAppSnippet != null) {
// load placeholder layout with OK button disabled until we override this layout in
// startInstallConfirm
+ bindUi();
checkIfAllowedAndInitiateInstall();
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
index b2a8b87a4495..960ebccf6c25 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
@@ -21,6 +21,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
data class CardButton(
val text: String,
+ val contentDescription: String? = null,
val onClick: () -> Unit,
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index c7845fa724d4..700fa487ed73 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -45,6 +45,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.debug.UiModePreviews
import com.android.settingslib.spa.framework.theme.SettingsDimension
@@ -182,7 +184,11 @@ private fun Buttons(buttons: List<CardButton>, color: Color) {
@Composable
private fun Button(button: CardButton, color: Color) {
- TextButton(onClick = button.onClick) {
+ TextButton(
+ onClick = button.onClick,
+ modifier =
+ Modifier.semantics { button.contentDescription?.let { this.contentDescription = it } }
+ ) {
Text(text = button.text, color = color)
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
index beb9433cdbf0..b5b2525bffdd 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
@@ -36,8 +36,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsCardTest {
- @get:Rule
- val composeTestRule = createComposeRule()
+ @get:Rule val composeTestRule = createComposeRule()
private val context: Context = ApplicationProvider.getApplicationContext()
@@ -76,9 +75,7 @@ class SettingsCardTest {
CardModel(
title = "",
text = "",
- buttons = listOf(
- CardButton(text = TEXT) {}
- ),
+ buttons = listOf(CardButton(text = TEXT) {}),
)
)
}
@@ -94,9 +91,7 @@ class SettingsCardTest {
CardModel(
title = "",
text = "",
- buttons = listOf(
- CardButton(text = TEXT) { buttonClicked = true }
- ),
+ buttons = listOf(CardButton(text = TEXT) { buttonClicked = true }),
)
)
}
@@ -107,6 +102,25 @@ class SettingsCardTest {
}
@Test
+ fun settingsCard_buttonHaveContentDescription() {
+ composeTestRule.setContent {
+ SettingsCard(
+ CardModel(
+ title = "",
+ text = "",
+ buttons = listOf(CardButton(
+ text = TEXT,
+ contentDescription = CONTENT_DESCRIPTION,
+ ) {}
+ ),
+ )
+ )
+ }
+
+ composeTestRule.onNodeWithContentDescription(CONTENT_DESCRIPTION).assertIsDisplayed()
+ }
+
+ @Test
fun settingsCard_dismiss() {
composeTestRule.setContent {
var isVisible by remember { mutableStateOf(true) }
@@ -130,5 +144,6 @@ class SettingsCardTest {
private companion object {
const val TITLE = "Title"
const val TEXT = "Text"
+ const val CONTENT_DESCRIPTION = "content-description"
}
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a4b3af990c17..2e64212d298a 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1340,11 +1340,11 @@
<string name="notice_header" translatable="false"></string>
<!-- Name of the phone device. [CHAR LIMIT=30] -->
- <string name="media_transfer_this_device_name" product="default">This phone</string>
+ <string name="media_transfer_this_device_name">This phone</string>
<!-- Name of the tablet device. [CHAR LIMIT=30] -->
- <string name="media_transfer_this_device_name" product="tablet">This tablet</string>
+ <string name="media_transfer_this_device_name_tablet">This tablet</string>
<!-- Name of the default media output of the TV. [CHAR LIMIT=30] -->
- <string name="media_transfer_this_device_name" product="tv">@string/tv_media_transfer_default</string>
+ <string name="media_transfer_this_device_name_tv">@string/tv_media_transfer_default</string>
<!-- Name of the dock device. [CHAR LIMIT=30] -->
<string name="media_transfer_dock_speaker_device_name">Dock speaker</string>
<!-- Default name of the external device. [CHAR LIMIT=30] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 15f33d2cff42..ba9180db0887 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -37,6 +37,7 @@ import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;
+import android.os.SystemProperties;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -45,6 +46,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
import com.android.settingslib.media.flags.Flags;
+import java.util.Arrays;
import java.util.List;
/**
@@ -63,6 +65,17 @@ public class PhoneMediaDevice extends MediaDevice {
private final DeviceIconUtil mDeviceIconUtil;
+ /** Returns this device name for media transfer. */
+ public static @NonNull String getMediaTransferThisDeviceName(@NonNull Context context) {
+ if (isTv(context)) {
+ return context.getString(R.string.media_transfer_this_device_name_tv);
+ } else if (isTablet()) {
+ return context.getString(R.string.media_transfer_this_device_name_tablet);
+ } else {
+ return context.getString(R.string.media_transfer_this_device_name);
+ }
+ }
+
/** Returns the device name for the given {@code routeInfo}. */
public static String getSystemRouteNameFromType(
@NonNull Context context, @NonNull MediaRoute2Info routeInfo) {
@@ -80,7 +93,7 @@ public class PhoneMediaDevice extends MediaDevice {
name = context.getString(R.string.media_transfer_dock_speaker_device_name);
break;
case TYPE_BUILTIN_SPEAKER:
- name = context.getString(R.string.media_transfer_this_device_name);
+ name = getMediaTransferThisDeviceName(context);
break;
case TYPE_HDMI:
name = context.getString(isTv ? R.string.tv_media_transfer_default :
@@ -135,6 +148,11 @@ public class PhoneMediaDevice extends MediaDevice {
&& Flags.enableTvMediaOutputDialog();
}
+ static boolean isTablet() {
+ return Arrays.asList(SystemProperties.get("ro.build.characteristics").split(","))
+ .contains("tablet");
+ }
+
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index ceba9be70487..e2d58d660fd5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -24,6 +24,7 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
import static com.android.settingslib.media.PhoneMediaDevice.PHONE_ID;
import static com.android.settingslib.media.PhoneMediaDevice.USB_HEADSET_ID;
import static com.android.settingslib.media.PhoneMediaDevice.WIRED_HEADSET_ID;
+import static com.android.settingslib.media.PhoneMediaDevice.getMediaTransferThisDeviceName;
import static com.google.common.truth.Truth.assertThat;
@@ -114,7 +115,7 @@ public class PhoneMediaDeviceTest {
when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
assertThat(mPhoneMediaDevice.getName())
- .isEqualTo(mContext.getString(R.string.media_transfer_this_device_name));
+ .isEqualTo(getMediaTransferThisDeviceName(mContext));
}
@EnableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 54ab5d1e726f..0050676ace84 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -1083,5 +1083,25 @@
<!-- Allow SystemUI to listen for the capabilities defined in the linked xml -->
<property android:name="android.net.PROPERTY_SELF_CERTIFIED_CAPABILITIES"
android:value="@xml/self_certified_network_capabilities_both" />
+
+
+ <service
+ android:name="com.android.systemui.dreams.homecontrols.HomeControlsDreamService"
+ android:exported="false"
+ android:enabled="false"
+ android:label="@string/home_controls_dream_label"
+ android:description="@string/home_controls_dream_description"
+ android:permission="android.permission.BIND_DREAM_SERVICE"
+ android:icon="@drawable/controls_icon"
+ >
+
+ <intent-filter>
+ <action android:name="android.service.dreams.DreamService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data
+ android:name="android.service.dream"
+ android:resource="@xml/home_controls_dream_metadata" />
+ </service>
</application>
</manifest>
diff --git a/packages/SystemUI/aconfig/cross_device_control.aconfig b/packages/SystemUI/aconfig/cross_device_control.aconfig
new file mode 100644
index 000000000000..d3f14c129a0b
--- /dev/null
+++ b/packages/SystemUI/aconfig/cross_device_control.aconfig
@@ -0,0 +1,15 @@
+package: "com.android.systemui"
+
+flag {
+ name: "legacy_le_audio_sharing"
+ namespace: "pixel_cross_device_control"
+ description: "Gates the legacy le audio sharing UI."
+ bug: "322295262"
+}
+
+flag {
+ name: "enable_personal_le_audio_sharing"
+ namespace: "pixel_cross_device_control"
+ description: "Gates the personal le audio sharing UI in UMO."
+ bug: "322295480"
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 0ea2b1fed968..3db99f284e4c 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -370,3 +370,17 @@ flag {
description: "Enables the compose version of keyguard."
bug: "301968149"
}
+
+flag {
+ name: "enable_contextual_tip_for_power_off"
+ namespace: "systemui"
+ description: "Enables on-screen contextual tip about how to power off or restart phone"
+ bug: "322891421"
+}
+
+flag {
+ name: "enable_contextual_tip_for_take_screenshot"
+ namespace: "systemui"
+ description: "Enables on-screen contextual tip about how to take screenshot."
+ bug: "322891421"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index f9b91cf4c10b..bd539a740e81 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -20,6 +20,7 @@ package com.android.systemui.bouncer.ui.composable
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextField
@@ -36,16 +37,21 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onInterceptKeyBeforeSoftKeyboard
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformIconButton
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
+import com.android.systemui.res.R
/** UI for the input part of a password-requiring version of the bouncer. */
@Composable
@@ -64,6 +70,7 @@ internal fun PasswordBouncer(
val password: String by viewModel.password.collectAsState()
val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
+ val isImeSwitcherButtonVisible by viewModel.isImeSwitcherButtonVisible.collectAsState()
DisposableEffect(Unit) {
viewModel.onShown()
@@ -116,5 +123,28 @@ internal fun PasswordBouncer(
false
}
},
+ trailingIcon =
+ if (isImeSwitcherButtonVisible) {
+ { ImeSwitcherButton(viewModel, color) }
+ } else null
+ )
+}
+
+/** Button for changing the password input method (IME). */
+@Composable
+private fun ImeSwitcherButton(
+ viewModel: PasswordBouncerViewModel,
+ color: Color,
+) {
+ val context = LocalContext.current
+ PlatformIconButton(
+ onClick = { viewModel.onImeSwitcherButtonClicked(context.displayId) },
+ iconResource = R.drawable.ic_lockscreen_ime,
+ contentDescription = stringResource(R.string.accessibility_ime_switch_button),
+ colors =
+ IconButtonDefaults.filledIconButtonColors(
+ contentColor = color,
+ containerColor = Color.Transparent,
+ )
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 576596fa67fe..4e72dfef82e1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -160,14 +160,23 @@ fun CommunalHub(
gridCoordinates
) {
detectLongPressGesture { offset ->
- isButtonToEditWidgetsShowing = true
-
// Deduct both grid offset relative to its container and content offset.
val adjustedOffset =
gridCoordinates?.let {
offset - it.positionInWindow() - contentOffset
}
val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+ // Display the button only when the gesture initiates from widgets,
+ // the CTA tile, or an empty area on the screen. UMO/smartspace have
+ // their own long-press handlers. To prevent user confusion, we should
+ // not display this button.
+ if (
+ index == null ||
+ communalContent[index].isWidget() ||
+ communalContent[index] is CommunalContentModel.CtaTileInViewMode
+ ) {
+ isButtonToEditWidgetsShowing = true
+ }
val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
viewModel.setSelectedKey(key)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
new file mode 100644
index 000000000000..2ba78cfd7785
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.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.notifications.ui.composable
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import com.android.compose.nestedscroll.PriorityNestedScrollConnection
+
+/**
+ * A [NestedScrollConnection] that listens for all vertical scroll events and responds in the
+ * following way:
+ * - If you **scroll up**, it **first brings the [scrimOffset]** back to the [minScrimOffset] and
+ * then allows scrolling of the children (usually the content).
+ * - If you **scroll down**, it **first allows scrolling of the children** (usually the content) and
+ * then resets the [scrimOffset] to [maxScrimOffset].
+ */
+fun NotificationScrimNestedScrollConnection(
+ scrimOffset: () -> Float,
+ onScrimOffsetChanged: (Float) -> Unit,
+ minScrimOffset: () -> Float,
+ maxScrimOffset: Float,
+ contentHeight: () -> Float,
+ minVisibleScrimHeight: () -> Float,
+): PriorityNestedScrollConnection {
+ return PriorityNestedScrollConnection(
+ orientation = Orientation.Vertical,
+ // scrolling up and inner content is taller than the scrim, so scrim needs to
+ // expand; content can scroll once scrim is at the minScrimOffset.
+ canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
+ offsetAvailable < 0 &&
+ offsetBeforeStart == 0f &&
+ contentHeight() > minVisibleScrimHeight() &&
+ scrimOffset() > minScrimOffset()
+ },
+ // scrolling down and content is done scrolling to top. After that, the scrim
+ // needs to collapse; collapse the scrim until it is at the maxScrimOffset.
+ canStartPostScroll = { offsetAvailable, _ ->
+ offsetAvailable > 0 && scrimOffset() < maxScrimOffset
+ },
+ canStartPostFling = { false },
+ canContinueScroll = {
+ val currentHeight = scrimOffset()
+ minScrimOffset() < currentHeight && currentHeight < maxScrimOffset
+ },
+ canScrollOnFling = true,
+ onStart = { /* do nothing */},
+ onScroll = { offsetAvailable ->
+ val currentHeight = scrimOffset()
+ val amountConsumed =
+ if (offsetAvailable > 0) {
+ val amountLeft = maxScrimOffset - currentHeight
+ offsetAvailable.coerceAtMost(amountLeft)
+ } else {
+ val amountLeft = minScrimOffset() - currentHeight
+ offsetAvailable.coerceAtLeast(amountLeft)
+ }
+ onScrimOffsetChanged(currentHeight + amountConsumed)
+ amountConsumed
+ },
+ // Don't consume the velocity on pre/post fling
+ onStop = { 0f },
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index e835d3e576d5..0e08a198c71e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -20,32 +20,53 @@ package com.android.systemui.notifications.ui.composable
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
import com.android.systemui.notifications.ui.composable.Notifications.Form
+import com.android.systemui.scene.ui.composable.Gone
+import com.android.systemui.scene.ui.composable.Shade
+import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
@@ -100,33 +121,109 @@ fun SceneScope.NotificationStack(
@Composable
fun SceneScope.NotificationScrollingStack(
viewModel: NotificationsPlaceholderViewModel,
+ maxScrimTop: () -> Float,
modifier: Modifier = Modifier,
) {
+ val density = LocalDensity.current
val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
+ val expansionFraction by viewModel.expandFraction.collectAsState(0f)
- val contentHeight by viewModel.intrinsicContentHeight.collectAsState()
+ val navBarHeight =
+ with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
+ val statusBarHeight =
+ with(density) { WindowInsets.systemBars.asPaddingValues().calculateTopPadding().toPx() }
+ val displayCutoutHeight =
+ with(density) { WindowInsets.displayCutout.asPaddingValues().calculateTopPadding().toPx() }
+ val screenHeight =
+ with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } +
+ navBarHeight +
+ maxOf(statusBarHeight, displayCutoutHeight)
- val expansionFraction by viewModel.expandFraction.collectAsState(0f)
+ val contentHeight = viewModel.intrinsicContentHeight.collectAsState()
- Box(
- modifier =
- modifier
- .verticalNestedScrollToScene()
- .fillMaxWidth()
- .element(Notifications.Elements.NotificationScrim)
- .graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
- alpha = expansionFraction
- }
- .background(MaterialTheme.colorScheme.surface)
- .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f))
- ) {
- NotificationPlaceholder(
- viewModel = viewModel,
- form = Form.Stack,
- modifier = Modifier.fillMaxWidth().height { contentHeight.roundToInt() }
+ // the offset for the notifications scrim. Its upper bound is 0, and its lower bound is
+ // calculated in minScrimOffset. The scrim is the same height as the screen minus the
+ // height of the Shade Header, and at rest (scrimOffset = 0) its top bound is at maxScrimStartY.
+ // When fully expanded (scrimOffset = minScrimOffset), its top bound is at minScrimStartY,
+ // which is equal to the height of the Shade Header. Thus, when the scrim is fully expanded, the
+ // entire height of the scrim is visible on screen.
+ val scrimOffset = remember { mutableStateOf(0f) }
+
+ val minScrimTop = with(density) { ShadeHeader.Dimensions.CollapsedHeight.toPx() }
+
+ // The minimum offset for the scrim. The scrim is considered fully expanded when it
+ // is at this offset.
+ val minScrimOffset: () -> Float = { minScrimTop - maxScrimTop() }
+
+ // The height of the scrim visible on screen when it is in its resting (collapsed) state.
+ val minVisibleScrimHeight: () -> Float = { screenHeight - maxScrimTop() }
+
+ // we are not scrolled to the top unless the scrim is at its maximum offset.
+ LaunchedEffect(viewModel, scrimOffset) {
+ snapshotFlow { scrimOffset.value >= 0f }
+ .collect { isScrolledToTop -> viewModel.setScrolledToTop(isScrolledToTop) }
+ }
+
+ // if contentHeight drops below minimum visible scrim height while scrim is
+ // expanded, reset scrim offset.
+ LaunchedEffect(contentHeight, screenHeight, maxScrimTop, scrimOffset) {
+ snapshotFlow { contentHeight.value < minVisibleScrimHeight() && scrimOffset.value < 0f }
+ .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f }
+ }
+
+ Box(modifier = modifier.element(Notifications.Elements.NotificationScrim)) {
+ Spacer(
+ modifier =
+ Modifier.fillMaxSize()
+ .graphicsLayer {
+ shape = RoundedCornerShape(cornerRadius.dp)
+ clip = true
+ }
+ .drawBehind { drawRect(Color.Black, blendMode = BlendMode.DstOut) }
)
+ Box(
+ modifier =
+ Modifier.fillMaxSize()
+ .offset { IntOffset(0, scrimOffset.value.roundToInt()) }
+ .graphicsLayer {
+ shape = RoundedCornerShape(cornerRadius.dp)
+ clip = true
+ alpha =
+ if (layoutState.isTransitioningBetween(Gone, Shade)) {
+ (expansionFraction / 0.3f).coerceAtMost(1f)
+ } else 1f
+ }
+ .background(MaterialTheme.colorScheme.surface)
+ .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f))
+ ) {
+ NotificationPlaceholder(
+ viewModel = viewModel,
+ form = Form.Stack,
+ modifier =
+ Modifier.verticalNestedScrollToScene(
+ topBehavior = NestedScrollBehavior.EdgeWithPreview,
+ )
+ .nestedScroll(
+ remember(
+ scrimOffset,
+ maxScrimTop,
+ minScrimTop,
+ ) {
+ NotificationScrimNestedScrollConnection(
+ scrimOffset = { scrimOffset.value },
+ onScrimOffsetChanged = { scrimOffset.value = it },
+ minScrimOffset = minScrimOffset,
+ maxScrimOffset = 0f,
+ contentHeight = { contentHeight.value },
+ minVisibleScrimHeight = minVisibleScrimHeight,
+ )
+ }
+ )
+ .verticalScroll(rememberScrollState())
+ .fillMaxWidth()
+ .height { (contentHeight.value + navBarHeight).roundToInt() },
+ )
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index bbfe0fda049a..5531f9cc5589 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -36,6 +36,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
@@ -46,6 +47,11 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
@@ -56,7 +62,7 @@ import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
+import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.SceneKey
@@ -116,6 +122,8 @@ private fun SceneScope.QuickSettingsScene(
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
+ val cornerRadius by viewModel.notifications.cornerRadiusDp.collectAsState()
+
// TODO(b/280887232): implement the real UI.
Box(modifier = modifier.fillMaxSize()) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
@@ -234,10 +242,32 @@ private fun SceneScope.QuickSettingsScene(
}
}
}
- HeadsUpNotificationSpace(
- viewModel = viewModel.notifications,
- isPeekFromBottom = true,
- modifier = Modifier.padding(16.dp).fillMaxSize(),
+ // Scrim with height 0 aligned to bottom of the screen to facilitate shared element
+ // transition from Shade scene.
+ Box(
+ modifier =
+ Modifier.element(Notifications.Elements.NotificationScrim)
+ .fillMaxWidth()
+ .height(0.dp)
+ .graphicsLayer {
+ shape = RoundedCornerShape(cornerRadius.dp)
+ clip = true
+ alpha = 1f
+ }
+ .background(MaterialTheme.colorScheme.surface)
+ .align(Alignment.BottomCenter)
+ .onPlaced { coordinates: LayoutCoordinates ->
+ viewModel.notifications.onContentTopChanged(
+ coordinates.positionInWindow().y
+ )
+ val boundsInWindow = coordinates.boundsInWindow()
+ viewModel.notifications.onBoundsChanged(
+ left = boundsInWindow.left,
+ top = boundsInWindow.top,
+ right = boundsInWindow.right,
+ bottom = boundsInWindow.bottom,
+ )
+ }
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 747faabe514b..770d654a4c88 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -18,13 +18,10 @@ package com.android.systemui.scene.ui.composable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
@@ -66,12 +63,6 @@ constructor(
override fun SceneScope.Content(
modifier: Modifier,
) {
- Box(modifier = modifier) {
- Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
- HeadsUpNotificationSpace(
- viewModel = notificationsViewModel,
- modifier = Modifier.padding(16.dp).fillMaxSize(),
- )
- }
+ Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 1545372686c9..497fe873e87d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -32,6 +32,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
@@ -62,6 +63,7 @@ import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.util.animation.MeasurementInput
import javax.inject.Inject
import javax.inject.Named
+import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -154,62 +156,104 @@ private fun SceneScope.ShadeScene(
mediaHost: MediaHost,
modifier: Modifier = Modifier,
) {
- val localDensity = LocalDensity.current
+ val density = LocalDensity.current
val layoutWidth = remember { mutableStateOf(0) }
+ val maxNotifScrimTop = remember { mutableStateOf(0f) }
Box(
modifier =
modifier.element(Shade.Elements.Scrim).background(MaterialTheme.colorScheme.scrim),
)
Box {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.fillMaxWidth().clickable(onClick = { viewModel.onContentClicked() })
- ) {
- CollapsedShadeHeader(
- viewModel = viewModel.shadeHeaderViewModel,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
- modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
- )
- QuickSettings(
- modifier = Modifier.height(130.dp),
- viewModel.qsSceneAdapter,
- )
-
- if (viewModel.isMediaVisible()) {
- val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded)
- MediaCarousel(
- modifier =
- Modifier.height(mediaHeight).fillMaxWidth().layout { measurable, constraints
- ->
- val placeable = measurable.measure(constraints)
-
- // Notify controller to size the carousel for the current space
- mediaHost.measurementInput =
- MeasurementInput(placeable.width, placeable.height)
- mediaCarouselController.setSceneContainerSize(
- placeable.width,
- placeable.height
+ Layout(
+ contents =
+ listOf(
+ {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier =
+ Modifier.fillMaxWidth()
+ .clickable(onClick = { viewModel.onContentClicked() })
+ ) {
+ CollapsedShadeHeader(
+ viewModel = viewModel.shadeHeaderViewModel,
+ createTintedIconManager = createTintedIconManager,
+ createBatteryMeterViewController = createBatteryMeterViewController,
+ statusBarIconController = statusBarIconController,
+ modifier =
+ Modifier.padding(
+ horizontal = Shade.Dimensions.HorizontalPadding
+ )
+ )
+ QuickSettings(
+ modifier = Modifier.height(130.dp),
+ viewModel.qsSceneAdapter,
)
- layout(placeable.width, placeable.height) {
- placeable.placeRelative(0, 0)
+ if (viewModel.isMediaVisible()) {
+ val mediaHeight =
+ dimensionResource(R.dimen.qs_media_session_height_expanded)
+ MediaCarousel(
+ modifier =
+ Modifier.height(mediaHeight).fillMaxWidth().layout {
+ measurable,
+ constraints ->
+ val placeable = measurable.measure(constraints)
+
+ // Notify controller to size the carousel for the
+ // current space
+ mediaHost.measurementInput =
+ MeasurementInput(placeable.width, placeable.height)
+ mediaCarouselController.setSceneContainerSize(
+ placeable.width,
+ placeable.height
+ )
+
+ layout(placeable.width, placeable.height) {
+ placeable.placeRelative(0, 0)
+ }
+ },
+ mediaHost = mediaHost,
+ layoutWidth = layoutWidth.value,
+ layoutHeight = with(density) { mediaHeight.toPx() }.toInt(),
+ carouselController = mediaCarouselController,
+ )
}
- },
- mediaHost = mediaHost,
- layoutWidth = layoutWidth.value,
- layoutHeight = with(localDensity) { mediaHeight.toPx() }.toInt(),
- carouselController = mediaCarouselController,
+
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+ },
+ {
+ NotificationScrollingStack(
+ viewModel = viewModel.notifications,
+ maxScrimTop = { maxNotifScrimTop.value },
+ )
+ },
)
- }
+ ) { measurables, constraints ->
+ check(measurables.size == 2)
+ check(measurables[0].size == 1)
+ check(measurables[1].size == 1)
- Spacer(modifier = Modifier.height(16.dp))
- NotificationScrollingStack(
- viewModel = viewModel.notifications,
- modifier = Modifier.fillMaxWidth().weight(1f),
- )
+ val quickSettingsPlaceable = measurables[0][0].measure(constraints)
+
+ val notificationsMeasurable = measurables[1][0]
+ val notificationsScrimMaxHeight =
+ constraints.maxHeight - ShadeHeader.Dimensions.CollapsedHeight.roundToPx()
+ val notificationsPlaceable =
+ notificationsMeasurable.measure(
+ constraints.copy(
+ minHeight = notificationsScrimMaxHeight,
+ maxHeight = notificationsScrimMaxHeight
+ )
+ )
+
+ maxNotifScrimTop.value = quickSettingsPlaceable.height.toFloat()
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ quickSettingsPlaceable.placeRelative(x = 0, y = 0)
+ notificationsPlaceable.placeRelative(x = 0, y = maxNotifScrimTop.value.roundToInt())
+ }
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index 705754594903..b9b472f5ac6d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -30,6 +30,7 @@ import kotlinx.coroutines.launch
internal fun CoroutineScope.animateToScene(
layoutState: BaseSceneTransitionLayoutState,
target: SceneKey,
+ transitionKey: TransitionKey?,
): TransitionState.Transition? {
val transitionState = layoutState.transitionState
if (transitionState.currentScene == target) {
@@ -45,7 +46,7 @@ internal fun CoroutineScope.animateToScene(
}
return when (transitionState) {
- is TransitionState.Idle -> animate(layoutState, target)
+ is TransitionState.Idle -> animate(layoutState, target, transitionKey)
is TransitionState.Transition -> {
// A transition is currently running: first check whether `transition.toScene` or
// `transition.fromScene` is the same as our target scene, in which case the transition
@@ -67,7 +68,7 @@ internal fun CoroutineScope.animateToScene(
// The transition is in progress: start the canned animation at the same
// progress as it was in.
// TODO(b/290184746): Also take the current velocity into account.
- animate(layoutState, target, startProgress = progress)
+ animate(layoutState, target, transitionKey, startProgress = progress)
}
} else if (transitionState.fromScene == target) {
// There is a transition from [target] to another scene: simply animate the same
@@ -82,12 +83,18 @@ internal fun CoroutineScope.animateToScene(
null
} else {
// TODO(b/290184746): Also take the current velocity into account.
- animate(layoutState, target, startProgress = progress, reversed = true)
+ animate(
+ layoutState,
+ target,
+ transitionKey,
+ startProgress = progress,
+ reversed = true,
+ )
}
} else {
// Generic interruption; the current transition is neither from or to [target].
// TODO(b/290930950): Better handle interruptions here.
- animate(layoutState, target)
+ animate(layoutState, target, transitionKey)
}
}
}
@@ -96,6 +103,7 @@ internal fun CoroutineScope.animateToScene(
private fun CoroutineScope.animate(
layoutState: BaseSceneTransitionLayoutState,
target: SceneKey,
+ transitionKey: TransitionKey?,
startProgress: Float = 0f,
reversed: Boolean = false,
): TransitionState.Transition {
@@ -127,7 +135,7 @@ private fun CoroutineScope.animate(
// Change the current layout state to start this new transition. This will compute the
// TransformationSpec associated to this transition, which we need to initialize the Animatable
// that will actually animate it.
- layoutState.startTransition(transition)
+ layoutState.startTransition(transition, transitionKey)
// The transformation now contains the spec that we should use to instantiate the Animatable.
val animationSpec = layoutState.transformationSpec.progressSpec
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index c68519547c7e..2596d4a34b6f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -55,6 +55,7 @@ class SceneKey(
// Implementation of [UserActionResult].
override val toScene: SceneKey = this
+ override val transitionKey: TransitionKey? = null
override val distance: UserActionDistance? = null
override fun toString(): String {
@@ -104,3 +105,13 @@ class ValueKey(debugName: String, identity: Any = Object()) : Key(debugName, ide
return "ValueKey(debugName=$debugName)"
}
}
+
+/**
+ * Key for a transition. This can be used to specify which transition spec should be used when
+ * starting the transition between two scenes.
+ */
+class TransitionKey(debugName: String, identity: Any = Object()) : Key(debugName, identity) {
+ override fun toString(): String {
+ return "TransitionKey(debugName=$debugName)"
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 35754d624ccd..23af5acd0ee8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -54,7 +54,7 @@ internal class SceneGestureHandler(
private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
if (isDrivingTransition || force) {
- layoutState.startTransition(newTransition)
+ layoutState.startTransition(newTransition, newTransition.key)
// Initialize SwipeTransition.swipeSpec. Note that this must be called right after
// layoutState.startTransition() is called, because it computes the
@@ -237,7 +237,11 @@ internal class SceneGestureHandler(
}
swipeTransition.dragOffset += acceleratedOffset
- if (isNewFromScene || result.toScene != swipeTransition.toScene) {
+ if (
+ isNewFromScene ||
+ result.toScene != swipeTransition.toScene ||
+ result.transitionKey != swipeTransition.key
+ ) {
updateTransition(
SwipeTransition(fromScene, result).apply {
this.dragOffset = swipeTransition.dragOffset
@@ -484,6 +488,7 @@ internal class SceneGestureHandler(
private fun SwipeTransition(fromScene: Scene, result: UserActionResult): SwipeTransition {
return SwipeTransition(
+ result.transitionKey,
fromScene,
layoutImpl.scene(result.toScene),
computeAbsoluteDistance(fromScene, result),
@@ -491,6 +496,7 @@ internal class SceneGestureHandler(
}
internal class SwipeTransition(
+ val key: TransitionKey?,
val _fromScene: Scene,
val _toScene: Scene,
/**
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 7e0aa9c3e2b1..d904c8b770bf 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
@@ -388,8 +388,9 @@ interface SwipeSourceDetector {
/**
* The result of performing a [UserAction].
*
- * Note: [UserActionResult] is implemented by [SceneKey], and you can also use [withDistance] to
- * easily create a [UserActionResult] with a fixed distance:
+ * Note: [UserActionResult] is implemented by [SceneKey], so you can also use scene keys directly
+ * when defining your [UserActionResult]s.
+ *
* ```
* SceneTransitionLayout(...) {
* scene(
@@ -397,7 +398,7 @@ interface SwipeSourceDetector {
* userActions =
* mapOf(
* Swipe.Right to Scene.Bar,
- * Swipe.Down to Scene.Doe withDistance 100.dp,
+ * Swipe.Down to Scene.Doe,
* )
* )
* ) { ... }
@@ -408,6 +409,9 @@ interface UserActionResult {
/** The scene we should be transitioning to during the [UserAction]. */
val toScene: SceneKey
+ /** The key of the transition that should be used. */
+ val transitionKey: TransitionKey?
+
/**
* The distance the action takes to animate from 0% to 100%.
*
@@ -416,6 +420,32 @@ interface UserActionResult {
val distance: UserActionDistance?
}
+/** Create a [UserActionResult] to [toScene] with the given [distance] and [transitionKey]. */
+fun UserActionResult(
+ toScene: SceneKey,
+ distance: UserActionDistance? = null,
+ transitionKey: TransitionKey? = null,
+): UserActionResult {
+ return object : UserActionResult {
+ override val toScene: SceneKey = toScene
+ override val transitionKey: TransitionKey? = transitionKey
+ override val distance: UserActionDistance? = distance
+ }
+}
+
+/** Create a [UserActionResult] to [toScene] with the given fixed [distance] and [transitionKey]. */
+fun UserActionResult(
+ toScene: SceneKey,
+ distance: Dp,
+ transitionKey: TransitionKey? = null,
+): UserActionResult {
+ return UserActionResult(
+ toScene = toScene,
+ distance = FixedDistance(distance),
+ transitionKey = transitionKey,
+ )
+}
+
interface UserActionDistance {
/**
* Return the **absolute** distance of the user action given the size of the scene we are
@@ -424,22 +454,6 @@ interface UserActionDistance {
fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float
}
-/**
- * A utility function to make it possible to define user actions with a distance using the syntax
- * `Swipe.Up to Scene.foo withDistance 100.dp`
- */
-infix fun Pair<UserAction, SceneKey>.withDistance(
- distance: Dp
-): Pair<UserAction, UserActionResult> {
- val scene = second
- val distance = FixedDistance(distance)
- return first to
- object : UserActionResult {
- override val toScene: SceneKey = scene
- override val distance: UserActionDistance = distance
- }
-}
-
/** The user action has a fixed [absoluteDistance]. */
private class FixedDistance(private val distance: Dp) : UserActionDistance {
override fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float {
@@ -490,6 +504,7 @@ internal fun SceneTransitionLayoutForTesting(
layoutImpl.density = density
layoutImpl.swipeSourceDetector = swipeSourceDetector
+ layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
}
layoutImpl.Content(modifier)
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 956e326dc03e..aee6f9e15620 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
@@ -92,6 +92,7 @@ sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState
fun setTargetScene(
targetScene: SceneKey,
coroutineScope: CoroutineScope,
+ transitionKey: TransitionKey? = null,
): TransitionState.Transition?
}
@@ -213,11 +214,14 @@ internal abstract class BaseSceneTransitionLayoutState(initialScene: SceneKey) :
}
/** Start a new [transition], instantly interrupting any ongoing transition if there was one. */
- internal fun startTransition(transition: TransitionState.Transition) {
+ internal fun startTransition(
+ transition: TransitionState.Transition,
+ transitionKey: TransitionKey?,
+ ) {
// Compute the [TransformationSpec] when the transition starts.
transformationSpec =
transitions
- .transitionSpec(transition.fromScene, transition.toScene)
+ .transitionSpec(transition.fromScene, transition.toScene, key = transitionKey)
.transformationSpec()
transitionState = transition
@@ -265,7 +269,11 @@ internal class HoistedSceneTransitionLayoutScene(
// Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame
// late.
val newKey = targetSceneChannel.tryReceive().getOrNull() ?: newKey
- animateToScene(layoutState = this@HoistedSceneTransitionLayoutScene, newKey)
+ animateToScene(
+ layoutState = this@HoistedSceneTransitionLayoutScene,
+ target = newKey,
+ transitionKey = null,
+ )
}
}
}
@@ -278,14 +286,14 @@ internal class MutableSceneTransitionLayoutStateImpl(
) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene) {
override fun setTargetScene(
targetScene: SceneKey,
- coroutineScope: CoroutineScope
+ coroutineScope: CoroutineScope,
+ transitionKey: TransitionKey?,
): TransitionState.Transition? {
- return with(this) {
- coroutineScope.animateToScene(
- layoutState = this@MutableSceneTransitionLayoutStateImpl,
- target = targetScene,
- )
- }
+ return coroutineScope.animateToScene(
+ layoutState = this@MutableSceneTransitionLayoutStateImpl,
+ target = targetScene,
+ transitionKey = transitionKey,
+ )
}
override fun CoroutineScope.onChangeScene(scene: SceneKey) {
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 ac423b70fd50..b8f9359463de 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
@@ -42,32 +42,42 @@ internal constructor(
internal val defaultSwipeSpec: SpringSpec<Float>,
internal val transitionSpecs: List<TransitionSpecImpl>,
) {
- private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpecImpl>>()
+ private val cache =
+ mutableMapOf<
+ SceneKey, MutableMap<SceneKey, MutableMap<TransitionKey?, TransitionSpecImpl>>
+ >()
- internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpecImpl {
- return cache.getOrPut(from) { mutableMapOf() }.getOrPut(to) { findSpec(from, to) }
+ internal fun transitionSpec(
+ from: SceneKey,
+ to: SceneKey,
+ key: TransitionKey?,
+ ): TransitionSpecImpl {
+ return cache
+ .getOrPut(from) { mutableMapOf() }
+ .getOrPut(to) { mutableMapOf() }
+ .getOrPut(key) { findSpec(from, to, key) }
}
- private fun findSpec(from: SceneKey, to: SceneKey): TransitionSpecImpl {
- val spec = transition(from, to) { it.from == from && it.to == to }
+ private fun findSpec(from: SceneKey, to: SceneKey, key: TransitionKey?): TransitionSpecImpl {
+ val spec = transition(from, to, key) { it.from == from && it.to == to }
if (spec != null) {
return spec
}
- val reversed = transition(from, to) { it.from == to && it.to == from }
+ val reversed = transition(from, to, key) { it.from == to && it.to == from }
if (reversed != null) {
return reversed.reversed()
}
val relaxedSpec =
- transition(from, to) {
+ transition(from, to, key) {
(it.from == from && it.to == null) || (it.to == to && it.from == null)
}
if (relaxedSpec != null) {
return relaxedSpec
}
- return transition(from, to) {
+ return transition(from, to, key) {
(it.from == to && it.to == null) || (it.to == from && it.from == null)
}
?.reversed()
@@ -77,11 +87,12 @@ internal constructor(
private fun transition(
from: SceneKey,
to: SceneKey,
+ key: TransitionKey?,
filter: (TransitionSpecImpl) -> Boolean,
): TransitionSpecImpl? {
var match: TransitionSpecImpl? = null
transitionSpecs.fastForEach { spec ->
- if (filter(spec)) {
+ if (spec.key == key && filter(spec)) {
if (match != null) {
error("Found multiple transition specs for transition $from => $to")
}
@@ -92,7 +103,7 @@ internal constructor(
}
private fun defaultTransition(from: SceneKey, to: SceneKey) =
- TransitionSpecImpl(from, to, TransformationSpec.EmptyProvider)
+ TransitionSpecImpl(key = null, from, to, TransformationSpec.EmptyProvider)
companion object {
internal val DefaultSwipeSpec =
@@ -107,6 +118,9 @@ internal constructor(
/** The definition of a transition between [from] and [to]. */
interface TransitionSpec {
+ /** The key of this [TransitionSpec]. */
+ val key: TransitionKey?
+
/**
* The scene we are transitioning from. If `null`, this spec can be used to animate from any
* scene.
@@ -164,12 +178,14 @@ interface TransformationSpec {
}
internal class TransitionSpecImpl(
+ override val key: TransitionKey?,
override val from: SceneKey?,
override val to: SceneKey?,
private val transformationSpec: () -> TransformationSpecImpl,
) : TransitionSpec {
override fun reversed(): TransitionSpecImpl {
return TransitionSpecImpl(
+ key = key,
from = to,
to = from,
transformationSpec = {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 04e093721824..d93911d2de42 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -42,10 +42,14 @@ interface SceneTransitionsBuilder {
* any scene. For the animation specification to apply only when transitioning between two
* specific scenes, use [from] instead.
*
+ * If [key] is not `null`, then this transition will only be used if the same key is specified
+ * when triggering the transition.
+ *
* @see from
*/
fun to(
to: SceneKey,
+ key: TransitionKey? = null,
builder: TransitionBuilder.() -> Unit = {},
): TransitionSpec
@@ -55,7 +59,8 @@ interface SceneTransitionsBuilder {
* the destination scene via the [to] argument.
*
* When looking up which transition should be used when animating from scene A to scene B, we
- * pick the single transition matching one of these predicates (in order of importance):
+ * pick the single transition with the given [key] and matching one of these predicates (in
+ * order of importance):
* 1. from == A && to == B
* 2. to == A && from == B, which is then treated in reverse.
* 3. (from == A && to == null) || (from == null && to == B)
@@ -64,6 +69,7 @@ interface SceneTransitionsBuilder {
fun from(
from: SceneKey,
to: SceneKey? = null,
+ key: TransitionKey? = null,
builder: TransitionBuilder.() -> Unit = {},
): TransitionSpec
}
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 df186a15181e..9b16d46bfcc8 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
@@ -49,21 +49,27 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
val transitionSpecs = mutableListOf<TransitionSpecImpl>()
- override fun to(to: SceneKey, builder: TransitionBuilder.() -> Unit): TransitionSpec {
- return transition(from = null, to = to, builder)
+ override fun to(
+ to: SceneKey,
+ key: TransitionKey?,
+ builder: TransitionBuilder.() -> Unit
+ ): TransitionSpec {
+ return transition(from = null, to = to, key = key, builder)
}
override fun from(
from: SceneKey,
to: SceneKey?,
+ key: TransitionKey?,
builder: TransitionBuilder.() -> Unit
): TransitionSpec {
- return transition(from = from, to = to, builder)
+ return transition(from = from, to = to, key = key, builder)
}
private fun transition(
from: SceneKey?,
to: SceneKey?,
+ key: TransitionKey?,
builder: TransitionBuilder.() -> Unit,
): TransitionSpec {
fun transformationSpec(): TransformationSpecImpl {
@@ -75,7 +81,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
)
}
- val spec = TransitionSpecImpl(from, to, ::transformationSpec)
+ val spec = TransitionSpecImpl(key, from, to, ::transformationSpec)
transitionSpecs.add(spec)
return spec
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index e8cc0eca0d8b..2dc94a405ae3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -38,6 +38,8 @@ import com.android.compose.test.MonotonicClockTestScope
import com.android.compose.test.runMonotonicClockTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,7 +49,7 @@ private val LAYOUT_SIZE = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt())
@RunWith(AndroidJUnit4::class)
class SceneGestureHandlerTest {
private class TestGestureScope(
- val coroutineScope: MonotonicClockTestScope,
+ private val testScope: MonotonicClockTestScope,
) {
private val layoutState =
MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
@@ -92,17 +94,16 @@ class SceneGestureHandlerTest {
swipeSourceDetector = DefaultEdgeDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenesBuilder,
- coroutineScope = coroutineScope,
+ coroutineScope = testScope,
)
.apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }
val sceneGestureHandler = layoutImpl.gestureHandler(Orientation.Vertical)
val horizontalSceneGestureHandler = layoutImpl.gestureHandler(Orientation.Horizontal)
- val draggable = sceneGestureHandler.draggable
fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) =
SceneNestedScrollHandler(
- layoutImpl,
+ layoutImpl = layoutImpl,
orientation = sceneGestureHandler.orientation,
topOrLeftBehavior = nestedScrollBehavior,
bottomOrRightBehavior = nestedScrollBehavior,
@@ -117,8 +118,12 @@ class SceneGestureHandlerTest {
fun up(fractionOfScreen: Float) =
if (fractionOfScreen < 0f) error("use down()") else -down(fractionOfScreen)
- // Offset y: 10% of the screen
- val offsetY10 = Offset(x = 0f, y = down(0.1f))
+ fun downOffset(fractionOfScreen: Float) =
+ if (fractionOfScreen < 0f) {
+ error("upOffset() is required, not implemented yet")
+ } else {
+ Offset(x = 0f, y = down(fractionOfScreen))
+ }
val transitionState: TransitionState
get() = layoutState.transitionState
@@ -127,15 +132,15 @@ class SceneGestureHandlerTest {
get() = (transitionState as Transition).progress
fun advanceUntilIdle() {
- coroutineScope.testScheduler.advanceUntilIdle()
+ testScope.testScheduler.advanceUntilIdle()
}
fun runCurrent() {
- coroutineScope.testScheduler.runCurrent()
+ testScope.testScheduler.runCurrent()
}
fun assertIdle(currentScene: SceneKey) {
- assertWithMessage("transitionState must be Idle").that(transitionState is Idle).isTrue()
+ assertThat(transitionState).isInstanceOf(Idle::class.java)
assertWithMessage("currentScene does not match")
.that(transitionState.currentScene)
.isEqualTo(currentScene)
@@ -148,11 +153,8 @@ class SceneGestureHandlerTest {
progress: Float? = null,
isUserInputOngoing: Boolean? = null
) {
- val transition = transitionState
- assertWithMessage("transitionState must be Transition")
- .that(transition is Transition)
- .isTrue()
- transition as Transition
+ assertThat(transitionState).isInstanceOf(Transition::class.java)
+ val transition = transitionState as Transition
if (currentScene != null)
assertWithMessage("currentScene does not match")
@@ -180,47 +182,115 @@ class SceneGestureHandlerTest {
.that(transition.isUserInputOngoing)
.isEqualTo(isUserInputOngoing)
}
+
+ fun onDragStarted(
+ startedPosition: Offset = Offset.Zero,
+ overSlop: Float,
+ pointersDown: Int = 1
+ ) {
+ // overSlop should be 0f only if the drag gesture starts with startDragImmediately
+ if (overSlop == 0f) error("Consider using onDragStartedImmediately()")
+ onDragStarted(sceneGestureHandler.draggable, startedPosition, overSlop, pointersDown)
+ }
+
+ fun onDragStartedImmediately(startedPosition: Offset = Offset.Zero, pointersDown: Int = 1) {
+ onDragStarted(
+ sceneGestureHandler.draggable,
+ startedPosition,
+ overSlop = 0f,
+ pointersDown
+ )
+ }
+
+ fun onDragStarted(
+ draggableHandler: DraggableHandler,
+ startedPosition: Offset = Offset.Zero,
+ overSlop: Float = 0f,
+ pointersDown: Int = 1
+ ) {
+ draggableHandler.onDragStarted(
+ startedPosition = startedPosition,
+ overSlop = overSlop,
+ pointersDown = pointersDown,
+ )
+
+ // MultiPointerDraggable will always call onDelta with the initial overSlop right after
+ onDelta(pixels = overSlop)
+ }
+
+ fun onDelta(pixels: Float) {
+ sceneGestureHandler.draggable.onDelta(pixels = pixels)
+ }
+
+ fun onDragStopped(velocity: Float) {
+ sceneGestureHandler.draggable.onDragStopped(velocity = velocity)
+ runCurrent()
+ }
+
+ fun NestedScrollConnection.scroll(
+ available: Offset,
+ consumedByScroll: Offset = Offset.Zero,
+ ) {
+ val consumedByPreScroll =
+ onPreScroll(
+ available = available,
+ source = NestedScrollSource.Drag,
+ )
+ val consumed = consumedByPreScroll + consumedByScroll
+
+ onPostScroll(
+ consumed = consumed,
+ available = available - consumed,
+ source = NestedScrollSource.Drag
+ )
+ }
+
+ fun NestedScrollConnection.preFling(
+ available: Velocity,
+ coroutineScope: CoroutineScope = testScope,
+ ) {
+ // onPreFling is a suspend function that returns the consumed velocity once it finishes
+ // consuming it. In the current scenario, it returns after completing the animation.
+ // To return immediately, we can initiate a job that allows us to check the status
+ // before the animation starts.
+ coroutineScope.launch { onPreFling(available = available) }
+ runCurrent()
+ }
}
private fun runGestureTest(block: suspend TestGestureScope.() -> Unit) {
- runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
- }
+ runMonotonicClockTest {
+ val testGestureScope = TestGestureScope(testScope = this)
- private fun DraggableHandler.onDragStarted(
- overSlop: Float = 0f,
- startedPosition: Offset = Offset.Zero,
- ) {
- onDragStarted(startedPosition, overSlop)
- // MultiPointerDraggable will always call onDelta with the initial overSlop right after
- onDelta(overSlop)
+ // run the test
+ testGestureScope.block()
+ }
}
@Test fun testPreconditions() = runGestureTest { assertIdle(currentScene = SceneA) }
@Test
fun onDragStarted_shouldStartATransition() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
}
@Test
fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
- draggable.onDelta(pixels = down(0.1f))
+ onDelta(pixels = down(fractionOfScreen = 0.1f))
assertThat(progress).isEqualTo(0.2f)
}
@Test
fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(
- velocity = velocityThreshold - 0.01f,
- )
+ onDragStopped(velocity = velocityThreshold - 0.01f)
assertTransition(currentScene = SceneA)
// wait for the stop animation
@@ -230,11 +300,10 @@ class SceneGestureHandlerTest {
@Test
fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(velocity = velocityThreshold)
-
+ onDragStopped(velocity = velocityThreshold)
assertTransition(currentScene = SceneC)
// wait for the stop animation
@@ -244,10 +313,10 @@ class SceneGestureHandlerTest {
@Test
fun onDragStoppedAfterStarted_returnToIdle() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(velocity = 0f)
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(currentScene = SceneA)
}
@@ -255,7 +324,7 @@ class SceneGestureHandlerTest {
@Test
fun onDragReversedDirection_changeToScene() = runGestureTest {
// Drag A -> B with progress 0.6
- draggable.onDragStarted(overSlop = -60f)
+ onDragStarted(overSlop = -60f)
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -264,7 +333,7 @@ class SceneGestureHandlerTest {
)
// Reverse direction such that A -> C now with 0.4
- draggable.onDelta(100f)
+ onDelta(pixels = 100f)
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -273,7 +342,7 @@ class SceneGestureHandlerTest {
)
// After the drag stopped scene C should be committed
- draggable.onDragStopped(velocity = velocityThreshold)
+ onDragStopped(velocity = velocityThreshold)
assertTransition(currentScene = SceneC, fromScene = SceneA, toScene = SceneC)
// wait for the stop animation
@@ -283,9 +352,12 @@ class SceneGestureHandlerTest {
@Test
fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
- horizontalSceneGestureHandler.draggable.onDragStarted(up(0.3f))
+ val horizontalDraggableHandler = horizontalSceneGestureHandler.draggable
+
+ onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f))
assertIdle(currentScene = SceneA)
- horizontalSceneGestureHandler.draggable.onDragStarted(down(0.3f))
+
+ onDragStarted(horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f))
assertIdle(currentScene = SceneA)
}
@@ -294,7 +366,7 @@ class SceneGestureHandlerTest {
navigateToSceneC()
// We are on SceneC which has no action in Down direction
- draggable.onDragStarted(10f)
+ onDragStarted(overSlop = 10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -303,7 +375,7 @@ class SceneGestureHandlerTest {
)
// Reverse drag direction, it will consume the previous drag
- draggable.onDelta(-10f)
+ onDelta(pixels = -10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -312,7 +384,7 @@ class SceneGestureHandlerTest {
)
// Continue reverse drag direction, it should record progress to Scene B
- draggable.onDelta(-10f)
+ onDelta(pixels = -10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -326,7 +398,10 @@ class SceneGestureHandlerTest {
navigateToSceneC()
// Start dragging from the bottom
- draggable.onDragStarted(up(0.1f), Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE))
+ onDragStarted(
+ startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE),
+ overSlop = up(fractionOfScreen = 0.1f)
+ )
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -337,14 +412,14 @@ class SceneGestureHandlerTest {
@Test
fun onDragToExactlyZero_toSceneIsSet() = runGestureTest {
- draggable.onDragStarted(down(0.3f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.3f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
toScene = SceneC,
progress = 0.3f
)
- draggable.onDelta(up(0.3f))
+ onDelta(pixels = up(fractionOfScreen = 0.3f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -355,8 +430,8 @@ class SceneGestureHandlerTest {
private fun TestGestureScope.navigateToSceneC() {
assertIdle(currentScene = SceneA)
- draggable.onDragStarted(down(1f))
- draggable.onDragStopped(0f)
+ onDragStarted(overSlop = down(fractionOfScreen = 1f))
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(currentScene = SceneC)
}
@@ -364,7 +439,7 @@ class SceneGestureHandlerTest {
@Test
fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
// Drag A -> B with progress 0.2
- draggable.onDragStarted(overSlop = up(0.2f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -373,13 +448,13 @@ class SceneGestureHandlerTest {
)
// Start animation A -> B with progress 0.2 -> 1.0
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
// While at A -> B do a 100% screen drag (progress 1.2). This should go past B and change
// the transition to B -> C with progress 0.2
- draggable.onDragStarted()
- draggable.onDelta(up(1f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 1f))
assertTransition(
currentScene = SceneB,
fromScene = SceneB,
@@ -388,7 +463,7 @@ class SceneGestureHandlerTest {
)
// After the drag stopped scene C should be committed
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneC, fromScene = SceneB, toScene = SceneC)
// wait for the stop animation
@@ -398,9 +473,9 @@ class SceneGestureHandlerTest {
@Test
fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
- draggable.onDragStarted(overSlop = up(0.2f))
- draggable.onDelta(up(0.2f))
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+ onDelta(pixels = up(fractionOfScreen = 0.2f))
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
mutableUserActionsA.remove(Swipe.Up)
@@ -409,83 +484,79 @@ class SceneGestureHandlerTest {
mutableUserActionsB.remove(Swipe.Down)
// start accelaratedScroll and scroll over to B -> null
- draggable.onDragStarted()
- draggable.onDelta(up(0.5f))
- draggable.onDelta(up(0.5f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
// here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
// still be called. Make sure that they don't crash or change the scene
- draggable.onDelta(up(0.5f))
- draggable.onDragStopped(0f)
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(SceneB)
// These events can still come in after the animation has settled
- draggable.onDelta(up(0.5f))
- draggable.onDragStopped(0f)
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDragStopped(velocity = 0f)
assertIdle(SceneB)
}
@Test
fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
mutableUserActionsA[Swipe.Up] = SceneC
- draggable.onDelta(up(0.1f))
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
// target stays B even though UserActions changed
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
- draggable.onDragStopped(down(0.1f))
+ onDragStopped(velocity = down(fractionOfScreen = 0.1f))
advanceUntilIdle()
// now target changed to C for new drag
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.1f)
}
@Test
fun onDragTargetsChanged_targetsChangeWhenStartingNewDrag() = runGestureTest {
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
mutableUserActionsA[Swipe.Up] = SceneC
- draggable.onDelta(up(0.1f))
- draggable.onDragStopped(down(0.1f))
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
+ onDragStopped(velocity = down(fractionOfScreen = 0.1f))
// now target changed to C for new drag that started before previous drag settled to Idle
- draggable.onDragStarted(overSlop = 0f)
- draggable.onDelta(up(0.1f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
}
@Test
fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(
- velocity = velocityThreshold,
- )
-
- // The stop animation is not started yet
- assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
-
- runCurrent()
+ onDragStopped(velocity = velocityThreshold)
- assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
- assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
assertTransition(currentScene = SceneC)
+ assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
+ assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
// Start a new gesture while the offset is animating
- draggable.onDragStarted()
+ onDragStartedImmediately()
assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
}
@Test
fun onInitialPreScroll_EdgeWithOverscroll_doNotChangeState() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ source = NestedScrollSource.Drag
+ )
assertIdle(currentScene = SceneA)
}
@@ -509,40 +580,29 @@ class SceneGestureHandlerTest {
val consumed =
nestedScroll.onPostScroll(
consumed = Offset.Zero,
- available = offsetY10,
+ available = downOffset(fractionOfScreen = 0.1f),
source = NestedScrollSource.Drag
)
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
- assertThat(consumed).isEqualTo(offsetY10)
- }
-
- private fun NestedScrollConnection.scroll(
- available: Offset,
- consumedByScroll: Offset = Offset.Zero,
- ) {
- val consumedByPreScroll =
- onPreScroll(available = available, source = NestedScrollSource.Drag)
- val consumed = consumedByPreScroll + consumedByScroll
- onPostScroll(
- consumed = consumed,
- available = available - consumed,
- source = NestedScrollSource.Drag
- )
+ assertThat(consumed).isEqualTo(downOffset(fractionOfScreen = 0.1f))
}
@Test
fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
// start intercept preScroll
val consumed =
- nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ source = NestedScrollSource.Drag
+ )
assertThat(progress).isEqualTo(0.2f)
// do nothing on postScroll
@@ -553,12 +613,12 @@ class SceneGestureHandlerTest {
)
assertThat(progress).isEqualTo(0.2f)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertThat(progress).isEqualTo(0.3f)
assertTransition(currentScene = SceneA)
}
- private suspend fun TestGestureScope.preScrollAfterSceneTransition(
+ private fun TestGestureScope.preScrollAfterSceneTransition(
firstScroll: Float,
secondScroll: Float
) {
@@ -567,10 +627,13 @@ class SceneGestureHandlerTest {
nestedScroll.scroll(available = Offset(0f, firstScroll))
// stop scene transition (start the "stop animation")
- nestedScroll.onPreFling(available = Velocity.Zero)
+ nestedScroll.preFling(available = Velocity.Zero)
// a pre scroll event, that could be intercepted by SceneGestureHandler
- nestedScroll.onPreScroll(Offset(0f, secondScroll), NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = Offset(0f, secondScroll),
+ source = NestedScrollSource.Drag
+ )
}
@Test
@@ -610,16 +673,17 @@ class SceneGestureHandlerTest {
preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
+ advanceUntilIdle()
assertIdle(SceneB)
}
@Test
fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- nestedScroll.onPreFling(available = Velocity.Zero)
+ nestedScroll.preFling(available = Velocity.Zero)
assertTransition(currentScene = SceneA)
// wait for the stop animation
@@ -627,15 +691,15 @@ class SceneGestureHandlerTest {
assertIdle(currentScene = SceneA)
}
- private suspend fun TestGestureScope.flingAfterScroll(
+ private fun TestGestureScope.flingAfterScroll(
use: NestedScrollBehavior,
idleAfterScroll: Boolean,
) {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA)
- nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
}
@Test
@@ -679,19 +743,22 @@ class SceneGestureHandlerTest {
}
/** we started the scroll in the scene, then fling with the velocityThreshold */
- private suspend fun TestGestureScope.flingAfterScrollStartedInScene(
+ private fun TestGestureScope.flingAfterScrollStartedInScene(
use: NestedScrollBehavior,
idleAfterScroll: Boolean,
) {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use)
// scroll consumed in child
- nestedScroll.scroll(available = offsetY10, consumedByScroll = offsetY10)
+ nestedScroll.scroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ consumedByScroll = downOffset(fractionOfScreen = 0.1f)
+ )
// scroll offsetY10 is all available for parents
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA)
- nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
}
@Test
@@ -732,20 +799,20 @@ class SceneGestureHandlerTest {
@Test
fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest {
- draggable.onDelta(down(0.1f))
+ onDelta(pixels = down(fractionOfScreen = 0.1f))
assertIdle(currentScene = SceneA)
}
@Test
fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
- draggable.onDragStopped(velocityThreshold)
+ onDragStopped(velocity = velocityThreshold)
assertIdle(currentScene = SceneA)
}
@Test
fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.onPreFling(Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
assertIdle(currentScene = SceneA)
}
@@ -753,8 +820,10 @@ class SceneGestureHandlerTest {
fun startNestedScrollWhileDragging() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
+ val offsetY10 = downOffset(fractionOfScreen = 0.1f)
+
// Start a drag and then stop it, given that
- draggable.onDragStarted(overSlop = up(0.1f))
+ onDragStarted(overSlop = up(0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
@@ -764,7 +833,7 @@ class SceneGestureHandlerTest {
assertThat(progress).isEqualTo(0.2f)
// this should be ignored, we are scrolling now!
- draggable.onDragStopped(-velocityThreshold)
+ onDragStopped(-velocityThreshold)
assertTransition(currentScene = SceneA)
nestedScroll.scroll(available = -offsetY10)
@@ -773,7 +842,7 @@ class SceneGestureHandlerTest {
nestedScroll.scroll(available = -offsetY10)
assertThat(progress).isEqualTo(0.4f)
- nestedScroll.onPreFling(available = Velocity(0f, -velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, -velocityThreshold))
assertTransition(currentScene = SceneB)
// wait for the stop animation
@@ -788,7 +857,7 @@ class SceneGestureHandlerTest {
// Swipe up from the middle to transition to scene B.
val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- draggable.onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ onDragStarted(startedPosition = middle, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -802,7 +871,7 @@ class SceneGestureHandlerTest {
// should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
// should be 0f.
assertThat(sceneGestureHandler.shouldImmediatelyIntercept(middle)).isTrue()
- draggable.onDragStarted(startedPosition = middle, overSlop = 0f)
+ onDragStartedImmediately(startedPosition = middle)
// We should have intercepted the transition, so the transition should be the same object.
assertTransition(currentScene = SceneC, fromScene = SceneC, toScene = SceneB)
@@ -813,7 +882,7 @@ class SceneGestureHandlerTest {
// instead animate from C to A.
val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)
assertThat(sceneGestureHandler.shouldImmediatelyIntercept(bottom)).isFalse()
- draggable.onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
+ onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 9a25d43cb62d..c61917dbfb17 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -45,7 +45,10 @@ class SceneTransitionLayoutStateTest {
@Test
fun isTransitioningTo_transition() {
val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
- state.startTransition(transition(from = TestScenes.SceneA, to = TestScenes.SceneB))
+ state.startTransition(
+ transition(from = TestScenes.SceneA, to = TestScenes.SceneB),
+ transitionKey = null
+ )
assertThat(state.isTransitioning()).isTrue()
assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue()
@@ -116,4 +119,43 @@ class SceneTransitionLayoutStateTest {
testScheduler.advanceUntilIdle()
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
}
+
+ @Test
+ fun setTargetScene_withTransitionKey() = runMonotonicClockTest {
+ val transitionkey = TransitionKey(debugName = "foo")
+ val state =
+ MutableSceneTransitionLayoutState(
+ TestScenes.SceneA,
+ transitions =
+ transitions {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
+ from(TestScenes.SceneA, to = TestScenes.SceneB, key = transitionkey) {
+ fade(TestElements.Foo)
+ fade(TestElements.Bar)
+ }
+ },
+ )
+ as MutableSceneTransitionLayoutStateImpl
+
+ // Default transition from A to B.
+ assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+ assertThat(state.transformationSpec.transformations).hasSize(1)
+
+ // Go back to A.
+ state.setTargetScene(TestScenes.SceneA, coroutineScope = this)
+ testScheduler.advanceUntilIdle()
+ assertThat(state.currentTransition).isNull()
+ assertThat(state.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+ // Specific transition from A to B.
+ assertThat(
+ state.setTargetScene(
+ TestScenes.SceneB,
+ coroutineScope = this,
+ transitionKey = transitionkey,
+ )
+ )
+ .isNotNull()
+ assertThat(state.transformationSpec.transformations).hasSize(2)
+ }
}
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 af1a5b8f37b0..543ed0482430 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
@@ -384,7 +384,13 @@ class SwipeToSceneTest {
scene(
TestScenes.SceneA,
userActions =
- mapOf(Swipe.Down to TestScenes.SceneB withDistance verticalSwipeDistance),
+ mapOf(
+ Swipe.Down to
+ UserActionResult(
+ toScene = TestScenes.SceneB,
+ distance = verticalSwipeDistance,
+ )
+ ),
) {
Spacer(Modifier.fillMaxSize())
}
@@ -492,4 +498,54 @@ class SwipeToSceneTest {
}
assertThat(layoutState.currentTransition).isNotNull()
}
+
+ @Test
+ fun transitionKey() {
+ val transitionkey = TransitionKey(debugName = "foo")
+ val state =
+ MutableSceneTransitionLayoutState(
+ TestScenes.SceneA,
+ transitions {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
+ from(TestScenes.SceneA, to = TestScenes.SceneB, key = transitionkey) {
+ fade(TestElements.Foo)
+ fade(TestElements.Bar)
+ }
+ }
+ )
+ as MutableSceneTransitionLayoutStateImpl
+
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state, Modifier.size(LayoutWidth, LayoutHeight)) {
+ scene(
+ TestScenes.SceneA,
+ userActions =
+ mapOf(
+ Swipe.Down to TestScenes.SceneB,
+ Swipe.Up to
+ UserActionResult(TestScenes.SceneB, transitionKey = transitionkey)
+ )
+ ) {
+ Box(Modifier.fillMaxSize())
+ }
+ scene(TestScenes.SceneB) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ // Swipe down for the default transition from A to B.
+ rule.onRoot().performTouchInput {
+ down(middle)
+ moveBy(Offset(0f, touchSlop), delayMillis = 1_000)
+ }
+
+ assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+ assertThat(state.transformationSpec.transformations).hasSize(1)
+
+ // Move the pointer up to swipe to scene B using the new transition.
+ rule.onRoot().performTouchInput { moveBy(Offset(0f, -1.dp.toPx()), delayMillis = 1_000) }
+ assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+ assertThat(state.transformationSpec.transformations).hasSize(2)
+ }
}
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 140bacaec738..1beafcc2cbb2 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
@@ -177,7 +177,7 @@ class TransitionDslTest {
// to B we defined.
val transformations =
transitions
- .transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA)
+ .transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA, key = null)
.transformationSpec()
.transformations
@@ -207,7 +207,7 @@ class TransitionDslTest {
// A => B does not have a custom spec.
assertThat(
transitions
- .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneB)
+ .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneB, key = null)
.transformationSpec()
.swipeSpec
)
@@ -216,7 +216,7 @@ class TransitionDslTest {
// A => C has a custom swipe spec.
assertThat(
transitions
- .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneC)
+ .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneC, key = null)
.transformationSpec()
.swipeSpec
)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
index fbcd5b27836e..0245cf2a26b8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
@@ -3,7 +3,9 @@ package com.android.compose.test
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.TestMonotonicFrameClock
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
@@ -12,16 +14,38 @@ import kotlinx.coroutines.withContext
* function.
*
* The [TestCoroutineScheduler] is passed to provide the functionality to wait for idle.
+ *
+ * Note: Please refer to the documentation for [runTest], as this feature utilizes it. This will
+ * provide a comprehensive understanding of all its behaviors.
*/
-@OptIn(ExperimentalTestApi::class)
+@OptIn(ExperimentalTestApi::class, ExperimentalCoroutinesApi::class)
fun runMonotonicClockTest(block: suspend MonotonicClockTestScope.() -> Unit) = runTest {
- // We need a CoroutineScope (like a TestScope) to create a TestMonotonicFrameClock.
- withContext(TestMonotonicFrameClock(this)) {
- MonotonicClockTestScope(coroutineScope = this, testScheduler = testScheduler).block()
+ val testScope: TestScope = this
+
+ withContext(TestMonotonicFrameClock(coroutineScope = testScope)) {
+ val testScopeWithMonotonicFrameClock: CoroutineScope = this
+
+ val scope =
+ MonotonicClockTestScope(
+ testScope = testScopeWithMonotonicFrameClock,
+ testScheduler = testScope.testScheduler,
+ backgroundScope = backgroundScope,
+ )
+
+ // Run the test
+ scope.block()
}
}
+/**
+ * A coroutine scope that for launching test coroutines for Compose.
+ *
+ * @param testScheduler The delay-skipping scheduler used by the test dispatchers running the code
+ * in this scope (see [TestScope.testScheduler]).
+ * @param backgroundScope A scope for background work (see [TestScope.backgroundScope]).
+ */
class MonotonicClockTestScope(
- coroutineScope: CoroutineScope,
- val testScheduler: TestCoroutineScheduler
-) : CoroutineScope by coroutineScope
+ testScope: CoroutineScope,
+ val testScheduler: TestCoroutineScheduler,
+ val backgroundScope: CoroutineScope,
+) : CoroutineScope by testScope
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 6d6d57506a3a..2d71a6e50ac2 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -89,7 +89,7 @@ fun ComposeContentTestRule.testTransition(
SceneTransitionLayout(
currentScene,
onChangeScene,
- transitions { from(fromScene, to = toScene, transition) },
+ transitions { from(fromScene, to = toScene, builder = transition) },
layoutModifier,
) {
scene(fromScene, content = fromSceneContent)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt
index be6bb9c39299..107293e801cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt
@@ -24,6 +24,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class SysuiTestCaseSelfTest : SysuiTestCase() {
private val contextBeforeSetup = context
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 9287edf4ee51..c2f0c6f71169 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
@@ -31,6 +31,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
index c86c7470909b..fa47a02d78c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
@@ -37,6 +37,7 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
private val testUser1 = UserHandle.of(1)!!
@@ -58,15 +59,21 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun isEnabled_settingNotInitialized_returnsFalseByDefault() =
+ scope.runTest {
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+ runCurrent()
+
+ Truth.assertThat(actualValue).isFalse()
+ }
+
+ @Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
val actualValue by collectLastValue(underTest.isEnabled(testUser1))
- settings.putIntForUser(
- SETTING_NAME,
- ENABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
Truth.assertThat(actualValue).isTrue()
@@ -77,25 +84,13 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
scope.runTest {
val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1))
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
- settings.putIntForUser(
- SETTING_NAME,
- ENABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
Truth.assertThat(flowValues.size).isEqualTo(3)
@@ -108,26 +103,14 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser1.identifier
- )
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser2.identifier
- )
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
runCurrent()
Truth.assertThat(lastValueUser1).isFalse()
Truth.assertThat(lastValueUser2).isFalse()
- settings.putIntForUser(
- SETTING_NAME,
- ENABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
Truth.assertThat(lastValueUser1).isTrue()
@@ -141,11 +124,7 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
runCurrent()
Truth.assertThat(success).isTrue()
- val actualValue =
- settings.getIntForUser(
- SETTING_NAME,
- testUser1.identifier
- )
+ val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
Truth.assertThat(actualValue).isEqualTo(ENABLED)
}
@@ -156,11 +135,7 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
runCurrent()
Truth.assertThat(success).isTrue()
- val actualValue =
- settings.getIntForUser(
- SETTING_NAME,
- testUser1.identifier
- )
+ val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
Truth.assertThat(actualValue).isEqualTo(DISABLED)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 4853529229fe..3d8159e70061 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -24,6 +24,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -37,6 +38,7 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class ColorInversionRepositoryImplTest : SysuiTestCase() {
private val testUser1 = UserHandle.of(1)!!
@@ -58,6 +60,16 @@ class ColorInversionRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun isEnabled_settingNotInitialized_returnsFalseByDefault() =
+ scope.runTest {
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+ runCurrent()
+
+ Truth.assertThat(actualValue).isFalse()
+ }
+
+ @Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
val actualValue by collectLastValue(underTest.isEnabled(testUser1))
@@ -71,8 +83,7 @@ class ColorInversionRepositoryImplTest : SysuiTestCase() {
@Test
fun isEnabled_settingUpdated_valueUpdated() =
scope.runTest {
- val flowValues: List<Boolean> by
- collectValues(underTest.isEnabled(testUser1))
+ val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1))
settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
index ce22e288e292..ed3b4c0fe322 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
@@ -31,6 +31,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class UserA11yQsShortcutsRepositoryTest : SysuiTestCase() {
private val secureSettings = FakeSettings()
private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
index 39f0d570cb26..059620502a49 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
@@ -34,6 +34,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class FaceHelpMessageDeferralTest : SysuiTestCase() {
val threshold = .75f
@Mock lateinit var logger: BiometricMessageDeferralLogger
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
index a67b0931f171..d317aeb08e1e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
@@ -37,6 +37,7 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class EmergencyServicesRepositoryImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
index 4aea4f329858..db83b3ba12b1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
@@ -29,6 +29,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class PrimaryBouncerCallbackInteractorTest : SysuiTestCase() {
private val mPrimaryBouncerCallbackInteractor = PrimaryBouncerCallbackInteractor()
@Mock
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index c193d14220de..fbb5415402db 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -26,18 +27,27 @@ import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.inputmethod.data.model.InputMethodModel
+import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository
+import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.google.common.truth.Truth.assertThat
+import java.util.UUID
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -51,19 +61,22 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val authenticationInteractor = kosmos.authenticationInteractor
+ private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+ private val selectedUserInteractor by lazy { kosmos.selectedUserInteractor }
+ private val inputMethodInteractor by lazy { kosmos.inputMethodInteractor }
private val bouncerViewModel by lazy { kosmos.bouncerViewModel }
private val isInputEnabled = MutableStateFlow(true)
- private val underTest by lazy {
+ private val underTest =
PasswordBouncerViewModel(
viewModelScope = testScope.backgroundScope,
+ isInputEnabled = isInputEnabled.asStateFlow(),
interactor = bouncerInteractor,
- isInputEnabled.asStateFlow(),
+ inputMethodInteractor = inputMethodInteractor,
+ selectedUserInteractor = selectedUserInteractor,
)
- }
@Before
fun setUp() {
@@ -270,6 +283,52 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
assertThat(isTextFieldFocusRequested).isTrue()
}
+ @Test
+ fun isImeSwitcherButtonVisible() =
+ testScope.runTest {
+ val selectedUserId by collectLastValue(selectedUserInteractor.selectedUser)
+ selectUser(USER_INFOS.first())
+
+ enableInputMethodsForUser(checkNotNull(selectedUserId))
+
+ // Assert initial value, before the UI subscribes.
+ assertThat(underTest.isImeSwitcherButtonVisible.value).isFalse()
+
+ // Subscription starts; verify a fresh value is fetched.
+ val isImeSwitcherButtonVisible by collectLastValue(underTest.isImeSwitcherButtonVisible)
+ assertThat(isImeSwitcherButtonVisible).isTrue()
+
+ // Change the user, verify a fresh value is fetched.
+ selectUser(USER_INFOS.last())
+
+ assertThat(
+ inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(
+ checkNotNull(selectedUserId)
+ )
+ )
+ .isFalse()
+ assertThat(isImeSwitcherButtonVisible).isFalse()
+
+ // Enable IMEs and add another subscriber; verify a fresh value is fetched.
+ enableInputMethodsForUser(checkNotNull(selectedUserId))
+ val collector2 by collectLastValue(underTest.isImeSwitcherButtonVisible)
+ assertThat(collector2).isTrue()
+ }
+
+ @Test
+ fun onImeSwitcherButtonClicked() =
+ testScope.runTest {
+ val displayId = 7
+ assertThat(kosmos.fakeInputMethodRepository.inputMethodPickerShownDisplayId)
+ .isNotEqualTo(displayId)
+
+ underTest.onImeSwitcherButtonClicked(displayId)
+ runCurrent()
+
+ assertThat(kosmos.fakeInputMethodRepository.inputMethodPickerShownDisplayId)
+ .isEqualTo(displayId)
+ }
+
private fun TestScope.switchToScene(toScene: SceneKey) {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
val bouncerShown = currentScene?.key != SceneKey.Bouncer && toScene == SceneKey.Bouncer
@@ -310,8 +369,45 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
runCurrent()
}
+ private fun TestScope.selectUser(userInfo: UserInfo) {
+ kosmos.fakeUserRepository.selectedUser.value =
+ SelectedUserModel(
+ userInfo = userInfo,
+ selectionStatus = SelectionStatus.SELECTION_COMPLETE
+ )
+ advanceTimeBy(PasswordBouncerViewModel.DELAY_TO_FETCH_IMES)
+ }
+
+ private suspend fun enableInputMethodsForUser(userId: Int) {
+ kosmos.fakeInputMethodRepository.setEnabledInputMethods(
+ userId,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 0),
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 1),
+ )
+ assertThat(inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(userId)).isTrue()
+ }
+
+ private fun createInputMethodWithSubtypes(
+ auxiliarySubtypes: Int,
+ nonAuxiliarySubtypes: Int,
+ ): InputMethodModel {
+ return InputMethodModel(
+ imeId = UUID.randomUUID().toString(),
+ subtypes =
+ List(auxiliarySubtypes + nonAuxiliarySubtypes) {
+ InputMethodModel.Subtype(subtypeId = it, isAuxiliary = it < auxiliarySubtypes)
+ }
+ )
+ }
+
companion object {
private const val ENTER_YOUR_PASSWORD = "Enter your password"
private const val WRONG_PASSWORD = "Wrong password"
+
+ private val USER_INFOS =
+ listOf(
+ UserInfo(100, "First user", 0),
+ UserInfo(101, "Second user", 0),
+ )
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
index 55016bb1fc07..25a287c4cfff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
@@ -24,6 +24,7 @@ import org.junit.runner.RunWith
*/
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class PinInputViewModelTest : SysuiTestCase() {
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 92b75cb0f47d..1642e527aa15 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -20,6 +20,7 @@ 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.log.table.TableLogBuffer
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.util.mockito.KotlinArgumentCaptor
@@ -40,30 +41,36 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class CommunalMediaRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var mediaData: MediaData
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
+ private lateinit var underTest: CommunalMediaRepositoryImpl
private val mediaDataListenerCaptor: KotlinArgumentCaptor<MediaDataManager.Listener> by lazy {
KotlinArgumentCaptor(MediaDataManager.Listener::class.java)
}
- private lateinit var mediaRepository: CommunalMediaRepository
-
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
+ underTest =
+ CommunalMediaRepositoryImpl(
+ mediaDataManager,
+ tableLogBuffer,
+ )
}
@Test
fun hasAnyMediaOrRecommendation_defaultsToFalse() =
testScope.runTest {
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
- val mediaModel = collectLastValue(mediaRepository.mediaModel)
+ val mediaModel = collectLastValue(underTest.mediaModel)
runCurrent()
assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
}
@@ -71,13 +78,11 @@ class CommunalMediaRepositoryImplTest : SysuiTestCase() {
@Test
fun mediaModel_updatesWhenMediaDataLoaded() =
testScope.runTest {
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
// Listener is added
verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
// Initial value is false.
- val mediaModel = collectLastValue(mediaRepository.mediaModel)
+ val mediaModel = collectLastValue(underTest.mediaModel)
runCurrent()
assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
@@ -95,8 +100,6 @@ class CommunalMediaRepositoryImplTest : SysuiTestCase() {
@Test
fun mediaModel_updatesWhenMediaDataRemoved() =
testScope.runTest {
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
// Listener is added
verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
@@ -106,7 +109,7 @@ class CommunalMediaRepositoryImplTest : SysuiTestCase() {
runCurrent()
// Media active now returns true.
- val mediaModel = collectLastValue(mediaRepository.mediaModel)
+ val mediaModel = collectLastValue(underTest.mediaModel)
assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
// Change to media unavailable and notify the listener.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
index 820bfbfdf0a2..6bff0dc7bd9e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -25,6 +25,8 @@ import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.settings.UserFileManager
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
@@ -36,10 +38,15 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
private lateinit var underTest: CommunalPrefsRepositoryImpl
private val kosmos = testKosmos()
@@ -50,6 +57,8 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
userRepository = kosmos.fakeUserRepository
userRepository.setUserInfos(USER_INFOS)
@@ -66,6 +75,8 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
kosmos.testDispatcher,
userRepository,
userFileManager,
+ logcatLogBuffer("CommunalPrefsRepositoryImplTest"),
+ tableLogBuffer,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
index c4a8582d51b5..2911a50c2737 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.settings.FakeSettings
@@ -34,11 +35,15 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -63,6 +68,7 @@ class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
userRepository,
secureSettings,
logcatLogBuffer("CommunalTutorialRepositoryImplTest"),
+ tableLogBuffer,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index c979ca63950a..475179dfdb72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -198,13 +198,22 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun deleteWidget_removeWidgetId_andDeleteFromDb() =
+ fun deleteWidgetFromDb() =
testScope.runTest {
val id = 1
- underTest.deleteWidget(id)
+ underTest.deleteWidgetFromDb(id)
runCurrent()
verify(communalWidgetDao).deleteWidgetById(id)
+ }
+
+ @Test
+ fun deleteWidgetFromHost() =
+ testScope.runTest {
+ val id = 1
+ underTest.deleteWidgetFromHost(id)
+ runCurrent()
+
verify(appWidgetHost).deleteAppWidgetId(id)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index ee01bf9c26e4..c5485c5c5c33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -642,6 +642,53 @@ class CommunalInteractorTest : SysuiTestCase() {
}
@Test
+ fun isCommunalVisible() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Blank)
+ )
+ communalRepository.setTransitionState(transitionState)
+
+ // isCommunalVisible is false when not on communal.
+ val isCommunalVisible by collectLastValue(underTest.isCommunalVisible)
+ assertThat(isCommunalVisible).isEqualTo(false)
+
+ // Start transition to communal.
+ transitionState.value =
+ ObservableCommunalTransitionState.Transition(
+ fromScene = CommunalSceneKey.Blank,
+ toScene = CommunalSceneKey.Communal,
+ progress = flowOf(0f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ // isCommunalVisible is true once transition starts.
+ assertThat(isCommunalVisible).isEqualTo(true)
+
+ // Finish transition to communal
+ transitionState.value =
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+
+ // isCommunalVisible is true since we're on communal.
+ assertThat(isCommunalVisible).isEqualTo(true)
+
+ // Start transition away from communal.
+ transitionState.value =
+ ObservableCommunalTransitionState.Transition(
+ fromScene = CommunalSceneKey.Communal,
+ toScene = CommunalSceneKey.Blank,
+ progress = flowOf(1.0f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ // isCommunalVisible is still true as the false as soon as transition away runs.
+ assertThat(isCommunalVisible).isEqualTo(true)
+ }
+
+ @Test
fun testShowWidgetEditorStartsActivity() =
testScope.runTest {
underTest.showWidgetEditor()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
index 3aa99c40bd67..89a4c04015b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
@@ -23,16 +23,27 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
class CommunalAppWidgetHostTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var testableLooper: TestableLooper
private lateinit var underTest: CommunalAppWidgetHost
@@ -43,9 +54,11 @@ class CommunalAppWidgetHostTest : SysuiTestCase() {
underTest =
CommunalAppWidgetHost(
context = context,
+ backgroundScope = kosmos.applicationCoroutineScope,
hostId = 116,
interactionHandler = mock(),
- looper = testableLooper.looper
+ looper = testableLooper.looper,
+ logBuffer = logcatLogBuffer("CommunalAppWidgetHostTest"),
)
}
@@ -64,4 +77,23 @@ class CommunalAppWidgetHostTest : SysuiTestCase() {
assertThat(view).isNotNull()
assertThat(view.appWidgetId).isEqualTo(appWidgetId)
}
+
+ @Test
+ fun appWidgetIdToRemove_emit() =
+ testScope.runTest {
+ val appWidgetIdToRemove by collectLastValue(underTest.appWidgetIdToRemove)
+
+ // Nothing should be emitted yet
+ assertThat(appWidgetIdToRemove).isNull()
+
+ underTest.onAppWidgetRemoved(appWidgetId = 1)
+ runCurrent()
+
+ assertThat(appWidgetIdToRemove).isEqualTo(1)
+
+ underTest.onAppWidgetRemoved(appWidgetId = 2)
+ runCurrent()
+
+ assertThat(appWidgetIdToRemove).isEqualTo(2)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 273d1cd55626..cf727cf5e180 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -36,6 +36,7 @@ import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
@@ -82,6 +83,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
kosmos.communalInteractor,
mediaHost,
uiEventLogger,
+ logcatLogBuffer("CommunalEditModeViewModelTest"),
)
}
@@ -142,6 +144,49 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
}
@Test
+ fun deleteWidget() =
+ testScope.runTest {
+ tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+ // Widgets available.
+ val widgets =
+ listOf(
+ CommunalWidgetContentModel(
+ appWidgetId = 0,
+ priority = 30,
+ providerInfo = mock(),
+ ),
+ CommunalWidgetContentModel(
+ appWidgetId = 1,
+ priority = 20,
+ providerInfo = mock(),
+ ),
+ )
+ widgetRepository.setCommunalWidgets(widgets)
+
+ val communalContent by collectLastValue(underTest.communalContent)
+
+ // Widgets and CTA tile are shown.
+ assertThat(communalContent?.size).isEqualTo(3)
+ assertThat(communalContent?.get(0))
+ .isInstanceOf(CommunalContentModel.Widget::class.java)
+ assertThat(communalContent?.get(1))
+ .isInstanceOf(CommunalContentModel.Widget::class.java)
+ assertThat(communalContent?.get(2))
+ .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
+
+ underTest.onDeleteWidget(widgets.get(0).appWidgetId)
+
+ // Only one widget and CTA tile remain.
+ assertThat(communalContent?.size).isEqualTo(2)
+ val item = communalContent?.get(0)
+ val appWidgetId = if (item is CommunalContentModel.Widget) item.appWidgetId else null
+ assertThat(appWidgetId).isEqualTo(widgets.get(1).appWidgetId)
+ assertThat(communalContent?.get(1))
+ .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
+ }
+
+ @Test
fun reorderWidget_uiEventLogging_start() {
underTest.onReorderWidgetStart()
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 0723e8306f71..73d309118548 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -40,6 +40,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
@@ -99,6 +100,7 @@ class CommunalViewModelTest : SysuiTestCase() {
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
mediaHost,
+ logcatLogBuffer("CommunalViewModelTest"),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index a3654b6e8963..032d76f0dceb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -21,14 +21,21 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.applicationCoroutineScope
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.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -47,6 +54,8 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
@Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
+ private lateinit var appWidgetIdToRemove: MutableSharedFlow<Int>
+
private lateinit var underTest: CommunalAppWidgetHostStartable
@Before
@@ -54,6 +63,9 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+ appWidgetIdToRemove = MutableSharedFlow()
+ whenever(appWidgetHost.appWidgetIdToRemove).thenReturn(appWidgetIdToRemove)
+
underTest =
CommunalAppWidgetHostStartable(
appWidgetHost,
@@ -120,6 +132,38 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
}
}
+ @Test
+ fun removeAppWidgetReportedByHost() =
+ with(kosmos) {
+ testScope.runTest {
+ // Set up communal widgets
+ val widget1 =
+ mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(1) }
+ val widget2 =
+ mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(2) }
+ val widget3 =
+ mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(3) }
+ fakeCommunalWidgetRepository.setCommunalWidgets(listOf(widget1, widget2, widget3))
+
+ underTest.start()
+
+ // Assert communal widgets has 3
+ val communalWidgets by
+ collectLastValue(fakeCommunalWidgetRepository.communalWidgets)
+ assertThat(communalWidgets).containsExactly(widget1, widget2, widget3)
+
+ // Report app widget 1 to remove and assert widget removed
+ appWidgetIdToRemove.emit(1)
+ runCurrent()
+ assertThat(communalWidgets).containsExactly(widget2, widget3)
+
+ // Report app widget 3 to remove and assert widget removed
+ appWidgetIdToRemove.emit(3)
+ runCurrent()
+ assertThat(communalWidgets).containsExactly(widget2)
+ }
+ }
+
private suspend fun setCommunalAvailable(available: Boolean) =
with(kosmos) {
fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index b54c5bdae004..9536084fdb23 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -32,6 +32,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DeviceEntryRepositoryTest : SysuiTestCase() {
@Mock private lateinit var lockPatternUtils: LockPatternUtils
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index 32943a19be28..51db4513bb39 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -33,6 +33,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DeviceUnlockedInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
index 2c6c793c97f5..d9dcfdc7becb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
@@ -31,6 +31,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DreamOverlayCallbackControllerTest : SysuiTestCase() {
@Mock private lateinit var callback: DreamOverlayCallbackController.Callback
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
index d379dc6f3dc1..5ae8595581e5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
@@ -39,6 +39,7 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class DreamOverlayNotificationCountProviderTest extends SysuiTestCase {
@Mock
NotificationListener mNotificationListener;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 8bf878c23cde..b46f2aa61518 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -50,6 +50,7 @@ import java.util.Collection;
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class DreamOverlayStateControllerTest extends SysuiTestCase {
@Mock
DreamOverlayStateController.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
index 7ff345cdcf7e..ad353ce39918 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
@@ -37,6 +37,7 @@ import java.util.concurrent.Executor;
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class DreamOverlayStatusBarItemsProviderTest extends SysuiTestCase {
@Mock
DreamOverlayStatusBarItemsProvider.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
index e0c6ab20c6e1..cb5702ada88e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
@@ -42,6 +42,7 @@ import kotlinx.coroutines.CoroutineScope;
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class AssistantAttentionConditionTest extends SysuiTestCase {
@Mock
Condition.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
index 480754c17616..96d3c9397228 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
@@ -45,6 +45,7 @@ import kotlinx.coroutines.CoroutineScope;
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class DreamConditionTest extends SysuiTestCase {
@Mock
Condition.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
new file mode 100644
index 000000000000..efccf7a81ccd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
@@ -0,0 +1,41 @@
+/*
+ * 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
+
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
+import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.homeControlsComponentInteractor by
+ Kosmos.Fixture {
+ HomeControlsComponentInteractor(
+ selectedComponentRepository = selectedComponentRepository,
+ controlsComponent,
+ authorizedPanelsRepository = authorizedPanelsRepository,
+ userRepository = fakeUserRepository,
+ bgScope = applicationCoroutineScope,
+ )
+ }
+
+val Kosmos.controlsComponent by Kosmos.Fixture<ControlsComponent> { mock() }
+val Kosmos.controlsListingController by Kosmos.Fixture<ControlsListingController> { mock() }
+val Kosmos.authorizedPanelsRepository by Kosmos.Fixture<AuthorizedPanelsRepository> { mock() }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
new file mode 100644
index 000000000000..ce74a905ef66
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
@@ -0,0 +1,255 @@
+/*
+ * 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
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.ServiceInfo
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.FakeSelectedComponentRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HomeControlsComponentInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var controlsComponent: ControlsComponent
+ private lateinit var controlsListingController: ControlsListingController
+ private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
+ private lateinit var underTest: HomeControlsComponentInteractor
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var selectedComponentRepository: FakeSelectedComponentRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ userRepository = kosmos.fakeUserRepository
+ userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
+
+ controlsComponent = kosmos.controlsComponent
+ authorizedPanelsRepository = kosmos.authorizedPanelsRepository
+ controlsListingController = kosmos.controlsListingController
+ selectedComponentRepository = kosmos.selectedComponentRepository
+
+ selectedComponentRepository.setCurrentUserHandle(PRIMARY_USER.userHandle)
+ whenever(controlsComponent.getControlsListingController())
+ .thenReturn(Optional.of(controlsListingController))
+
+ underTest =
+ HomeControlsComponentInteractor(
+ selectedComponentRepository,
+ controlsComponent,
+ authorizedPanelsRepository,
+ userRepository,
+ kosmos.applicationCoroutineScope,
+ )
+ }
+
+ @Test
+ fun testPanelComponentReturnsComponentNameForSelectedItemByUser() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(authorizedPanelsRepository.getAuthorizedPanels())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+ val actualValue by collectLastValue(underTest.panelComponent)
+ assertThat(actualValue).isNull()
+ runServicesUpdate()
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ }
+ }
+
+ @Test
+ fun testPanelComponentReturnsComponentNameAsInitialValueWithoutServiceUpdate() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(authorizedPanelsRepository.getAuthorizedPanels())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+ whenever(controlsListingController.getCurrentServices())
+ .thenReturn(
+ listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ )
+ val actualValue by collectLastValue(underTest.panelComponent)
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ }
+ }
+
+ @Test
+ fun testPanelComponentReturnsNullForHomeControlsThatDoesNotSupportPanel() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(authorizedPanelsRepository.getAuthorizedPanels())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
+ val actualValue by collectLastValue(underTest.panelComponent)
+ assertThat(actualValue).isNull()
+ runServicesUpdate(false)
+ assertThat(actualValue).isNull()
+ }
+ }
+
+ @Test
+ fun testPanelComponentReturnsNullWhenPanelIsUnauthorized() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf())
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+ val actualValue by collectLastValue(underTest.panelComponent)
+ assertThat(actualValue).isNull()
+ runServicesUpdate()
+ assertThat(actualValue).isNull()
+ }
+ }
+
+ @Test
+ fun testPanelComponentReturnsComponentNameForDifferentUsers() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(authorizedPanelsRepository.getAuthorizedPanels())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ userRepository.setSelectedUserInfo(ANOTHER_USER)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
+ selectedComponentRepository.setCurrentUserHandle(ANOTHER_USER.userHandle)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+
+ val actualValue by collectLastValue(underTest.panelComponent)
+ assertThat(actualValue).isNull()
+ runServicesUpdate()
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ }
+ }
+
+ @Test
+ fun testPanelComponentReturnsNullWhenControlsComponentReturnsNullForListingController() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(authorizedPanelsRepository.getAuthorizedPanels())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ whenever(controlsComponent.getControlsListingController())
+ .thenReturn(Optional.empty())
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+ val actualValue by collectLastValue(underTest.panelComponent)
+ assertThat(actualValue).isNull()
+ }
+ }
+
+ private fun runServicesUpdate(hasPanelBoolean: Boolean = true) {
+ val listings =
+ listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = hasPanelBoolean))
+ val callback = withArgCaptor { verify(controlsListingController).addCallback(capture()) }
+ callback.onServicesUpdated(listings)
+ }
+
+ private fun ControlsServiceInfo(
+ componentName: ComponentName,
+ label: CharSequence,
+ hasPanel: Boolean
+ ): ControlsServiceInfo {
+ val serviceInfo =
+ ServiceInfo().apply {
+ applicationInfo = ApplicationInfo()
+ packageName = componentName.packageName
+ name = componentName.className
+ }
+ return FakeControlsServiceInfo(context, serviceInfo, label, hasPanel)
+ }
+
+ private class FakeControlsServiceInfo(
+ context: Context,
+ serviceInfo: ServiceInfo,
+ private val label: CharSequence,
+ hasPanel: Boolean
+ ) : ControlsServiceInfo(context, serviceInfo) {
+
+ init {
+ if (hasPanel) {
+ panelActivity = serviceInfo.componentName
+ }
+ }
+
+ override fun loadLabel(): CharSequence {
+ return label
+ }
+ }
+
+ companion object {
+ private const val PRIMARY_USER_ID = 0
+ private val PRIMARY_USER =
+ UserInfo(
+ /* id= */ PRIMARY_USER_ID,
+ /* name= */ "primary user",
+ /* flags= */ UserInfo.FLAG_PRIMARY
+ )
+
+ private const val ANOTHER_USER_ID = 1
+ private val ANOTHER_USER =
+ UserInfo(
+ /* id= */ ANOTHER_USER_ID,
+ /* name= */ "another user",
+ /* flags= */ UserInfo.FLAG_PRIMARY
+ )
+ private const val TEST_PACKAGE = "pkg"
+ private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
+ private const val TEST_PACKAGE_PANEL = "pkg.panel"
+ private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
+ private val TEST_SELECTED_COMPONENT_PANEL =
+ SelectedComponentRepository.SelectedComponent(
+ TEST_PACKAGE_PANEL,
+ TEST_COMPONENT_PANEL,
+ true
+ )
+ private val TEST_SELECTED_COMPONENT_NON_PANEL =
+ SelectedComponentRepository.SelectedComponent(
+ TEST_PACKAGE_PANEL,
+ TEST_COMPONENT_PANEL,
+ false
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
new file mode 100644
index 000000000000..d28b6bf39f30
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
@@ -0,0 +1,120 @@
+/*
+ * 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
+
+import android.app.Activity
+import android.content.ComponentName
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.settings.FakeControlsSettingsRepository
+import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.FakeLogBuffer.Factory.Companion.create
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HomeControlsDreamServiceTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
+ @Mock private lateinit var taskFragmentComponentFactory: TaskFragmentComponent.Factory
+ @Mock private lateinit var taskFragmentComponent: TaskFragmentComponent
+ @Mock private lateinit var activity: Activity
+ private val logBuffer: LogBuffer = create()
+
+ private lateinit var underTest: HomeControlsDreamService
+ private lateinit var homeControlsComponentInteractor: HomeControlsComponentInteractor
+ private lateinit var fakeDreamActivityProvider: DreamActivityProvider
+ private lateinit var controlsComponent: ControlsComponent
+ private lateinit var controlsListingController: ControlsListingController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ whenever(taskFragmentComponentFactory.create(any(), any(), any(), any()))
+ .thenReturn(taskFragmentComponent)
+
+ controlsSettingsRepository = FakeControlsSettingsRepository()
+ controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+
+ controlsComponent = kosmos.controlsComponent
+ controlsListingController = kosmos.controlsListingController
+
+ whenever(controlsComponent.getControlsListingController())
+ .thenReturn(Optional.of(controlsListingController))
+
+ homeControlsComponentInteractor = kosmos.homeControlsComponentInteractor
+
+ fakeDreamActivityProvider = DreamActivityProvider { activity }
+ underTest =
+ HomeControlsDreamService(
+ controlsSettingsRepository,
+ taskFragmentComponentFactory,
+ homeControlsComponentInteractor,
+ fakeDreamActivityProvider,
+ logBuffer
+ )
+ }
+
+ @Test
+ fun testOnAttachedToWindowCreatesTaskFragmentComponent() {
+ underTest.onAttachedToWindow()
+ verify(taskFragmentComponentFactory).create(any(), any(), any(), any())
+ }
+
+ @Test
+ fun testOnDetachedFromWindowDestroyTaskFragmentComponent() {
+ underTest.onAttachedToWindow()
+ underTest.onDetachedFromWindow()
+ verify(taskFragmentComponent).destroy()
+ }
+
+ @Test
+ fun testNotCreatingTaskFragmentComponentWhenActivityIsNull() {
+ fakeDreamActivityProvider = DreamActivityProvider { null }
+ underTest =
+ HomeControlsDreamService(
+ controlsSettingsRepository,
+ taskFragmentComponentFactory,
+ homeControlsComponentInteractor,
+ fakeDreamActivityProvider,
+ logBuffer
+ )
+
+ underTest.onAttachedToWindow()
+ verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any())
+ }
+
+ companion object {
+ private const val TEST_PACKAGE_PANEL = "pkg.panel"
+ private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
new file mode 100644
index 000000000000..6610e7007eb2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
@@ -0,0 +1,202 @@
+/*
+ * 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
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
+import android.content.pm.UserInfo
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
+import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import java.util.Optional
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HomeControlsDreamStartableTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ @Mock private lateinit var packageManager: PackageManager
+
+ private lateinit var homeControlsComponentInteractor: HomeControlsComponentInteractor
+ private lateinit var selectedComponentRepository: SelectedComponentRepository
+ private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var controlsComponent: ControlsComponent
+ private lateinit var controlsListingController: ControlsListingController
+
+ private lateinit var startable: HomeControlsDreamStartable
+ private val componentName = ComponentName(context, HomeControlsDreamService::class.java)
+ private val testScope = kosmos.testScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ selectedComponentRepository = kosmos.selectedComponentRepository
+ authorizedPanelsRepository = kosmos.authorizedPanelsRepository
+ userRepository = kosmos.fakeUserRepository
+ controlsComponent = kosmos.controlsComponent
+ controlsListingController = kosmos.controlsListingController
+
+ userRepository.setUserInfos(listOf(PRIMARY_USER))
+
+ whenever(authorizedPanelsRepository.getAuthorizedPanels())
+ .thenReturn(setOf(TEST_PACKAGE_PANEL))
+
+ whenever(controlsComponent.getControlsListingController())
+ .thenReturn(Optional.of(controlsListingController))
+ whenever(controlsListingController.getCurrentServices())
+ .thenReturn(listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)))
+
+ homeControlsComponentInteractor = kosmos.homeControlsComponentInteractor
+
+ startable =
+ HomeControlsDreamStartable(
+ mContext,
+ packageManager,
+ homeControlsComponentInteractor,
+ kosmos.applicationCoroutineScope
+ )
+ }
+
+ @Test
+ @EnableFlags(FLAG_HOME_PANEL_DREAM)
+ fun testStartEnablesHomeControlsDreamServiceWhenPanelComponentIsNotNull() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+ startable.start()
+ runCurrent()
+ verify(packageManager)
+ .setComponentEnabledSetting(
+ eq(componentName),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
+ eq(PackageManager.DONT_KILL_APP)
+ )
+ }
+
+ @Test
+ @EnableFlags(FLAG_HOME_PANEL_DREAM)
+ fun testStartDisablesHomeControlsDreamServiceWhenPanelComponentIsNull() =
+ testScope.runTest {
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
+ startable.start()
+ runCurrent()
+ verify(packageManager)
+ .setComponentEnabledSetting(
+ eq(componentName),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+ eq(PackageManager.DONT_KILL_APP)
+ )
+ }
+
+ @Test
+ @DisableFlags(FLAG_HOME_PANEL_DREAM)
+ fun testStartDoesNotRunDreamServiceWhenFlagIsDisabled() =
+ testScope.runTest {
+ selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
+ startable.start()
+ runCurrent()
+ verify(packageManager, never()).setComponentEnabledSetting(any(), any(), any())
+ }
+
+ private fun ControlsServiceInfo(
+ componentName: ComponentName,
+ label: CharSequence,
+ hasPanel: Boolean
+ ): ControlsServiceInfo {
+ val serviceInfo =
+ ServiceInfo().apply {
+ applicationInfo = ApplicationInfo()
+ packageName = componentName.packageName
+ name = componentName.className
+ }
+ return FakeControlsServiceInfo(context, serviceInfo, label, hasPanel)
+ }
+
+ private class FakeControlsServiceInfo(
+ context: Context,
+ serviceInfo: ServiceInfo,
+ private val label: CharSequence,
+ hasPanel: Boolean
+ ) : ControlsServiceInfo(context, serviceInfo) {
+
+ init {
+ if (hasPanel) {
+ panelActivity = serviceInfo.componentName
+ }
+ }
+
+ override fun loadLabel(): CharSequence {
+ return label
+ }
+ }
+
+ companion object {
+ private const val PRIMARY_USER_ID = 0
+ private val PRIMARY_USER =
+ UserInfo(
+ /* id= */ PRIMARY_USER_ID,
+ /* name= */ "primary user",
+ /* flags= */ UserInfo.FLAG_PRIMARY
+ )
+ private const val TEST_PACKAGE_PANEL = "pkg.panel"
+ private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
+ private val TEST_SELECTED_COMPONENT_PANEL =
+ SelectedComponentRepository.SelectedComponent(
+ TEST_PACKAGE_PANEL,
+ TEST_COMPONENT_PANEL,
+ true
+ )
+ private val TEST_SELECTED_COMPONENT_NON_PANEL =
+ SelectedComponentRepository.SelectedComponent(
+ TEST_PACKAGE_PANEL,
+ TEST_COMPONENT_PANEL,
+ false
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index 74c197075461..2a9bc4a1d80e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -28,7 +28,6 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.shared.system.InputChannelCompat;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import org.junit.Before;
@@ -47,8 +46,6 @@ public class CommunalTouchHandlerTest extends SysuiTestCase {
@Mock
CentralSurfaces mCentralSurfaces;
@Mock
- NotificationShadeWindowController mNotificationShadeWindowController;
- @Mock
DreamTouchHandler.TouchSession mTouchSession;
CommunalTouchHandler mTouchHandler;
@@ -59,17 +56,10 @@ public class CommunalTouchHandlerTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
mTouchHandler = new CommunalTouchHandler(
Optional.of(mCentralSurfaces),
- mNotificationShadeWindowController,
INITIATION_WIDTH);
}
@Test
- public void testSessionStartForcesShadeOpen() {
- mTouchHandler.onSessionStart(mTouchSession);
- verify(mNotificationShadeWindowController).setForcePluginOpen(true, mTouchHandler);
- }
-
- @Test
public void testEventPropagation() {
final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
index 017fdbe8ac01..97052a84a60f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
@@ -39,6 +39,7 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class BouncerlessScrimControllerTest extends SysuiTestCase {
@Mock
BouncerlessScrimController.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
index 4ee4a60fbeaf..ebbcf981b762 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
@@ -39,6 +39,7 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.EnabledOnRavenwood
public class ScrimManagerTest extends SysuiTestCase {
@Mock
ScrimController mBouncerlessScrimController;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt
index 61b205710873..db52a45b59da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt
@@ -28,6 +28,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class FoldPostureTest : SysuiTestCase() {
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
new file mode 100644
index 000000000000..857cdce448ed
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.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.inputmethod.data.repository
+
+import android.os.UserHandle
+import android.view.inputmethod.InputMethodInfo
+import android.view.inputmethod.InputMethodManager
+import android.view.inputmethod.InputMethodSubtype
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.count
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InputMethodRepositoryTest : SysuiTestCase() {
+
+ @Mock private lateinit var inputMethodManager: InputMethodManager
+
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: InputMethodRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
+ .thenReturn(listOf())
+
+ underTest =
+ InputMethodRepositoryImpl(
+ backgroundDispatcher = kosmos.testDispatcher,
+ inputMethodManager = inputMethodManager,
+ )
+ }
+
+ @Test
+ fun enabledInputMethods_noImes_emptyFlow() =
+ testScope.runTest {
+ whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
+ .thenReturn(listOf())
+ whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
+ .thenReturn(listOf())
+
+ assertThat(underTest.enabledInputMethods(USER_ID, fetchSubtypes = true).count())
+ .isEqualTo(0)
+ }
+
+ @Test
+ fun selectedInputMethodSubtypes_returnsSubtypeList() =
+ testScope.runTest {
+ val subtypeId = 123
+ val isAuxiliary = true
+ whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
+ .thenReturn(listOf(mock<InputMethodInfo>()))
+ whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
+ .thenReturn(listOf())
+ whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
+ .thenReturn(
+ listOf(
+ InputMethodSubtype.InputMethodSubtypeBuilder()
+ .setSubtypeId(subtypeId)
+ .setIsAuxiliary(isAuxiliary)
+ .build()
+ )
+ )
+
+ val result = underTest.selectedInputMethodSubtypes()
+ assertThat(result).hasSize(1)
+ assertThat(result.first().subtypeId).isEqualTo(subtypeId)
+ assertThat(result.first().isAuxiliary).isEqualTo(isAuxiliary)
+ }
+
+ @Test
+ fun showImePicker_forwardsDisplayId() =
+ testScope.runTest {
+ val displayId = 7
+
+ underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes = */ true)
+
+ verify(inputMethodManager)
+ .showInputMethodPickerFromSystem(
+ /* showAuxiliarySubtypes = */ eq(true),
+ /* displayId = */ eq(displayId)
+ )
+ }
+
+ companion object {
+ private const val USER_ID = 100
+ private val USER_HANDLE = UserHandle.of(USER_ID)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
new file mode 100644
index 000000000000..d23ff2a817e9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.inputmethod.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.inputmethod.data.model.InputMethodModel
+import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository
+import com.android.systemui.inputmethod.data.repository.inputMethodRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.google.common.truth.Truth.assertThat
+import java.util.UUID
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InputMethodInteractorTest : SysuiTestCase() {
+
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val fakeInputMethodRepository = kosmos.fakeInputMethodRepository
+
+ private val underTest = InputMethodInteractor(repository = kosmos.inputMethodRepository)
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_noImes_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(USER_ID)
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_noMatches_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 1, nonAuxiliarySubtypes = 0),
+ createInputMethodWithSubtypes(auxiliarySubtypes = 3, nonAuxiliarySubtypes = 0),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_oneMatch_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 0),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_twoMatches_returnsTrue() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 1),
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 0),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isTrue()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_oneWithNonAux_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 0, nonAuxiliarySubtypes = 2),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_twoWithAux_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.setEnabledInputMethods(
+ USER_ID,
+ createInputMethodWithSubtypes(auxiliarySubtypes = 3, nonAuxiliarySubtypes = 0),
+ createInputMethodWithSubtypes(auxiliarySubtypes = 5, nonAuxiliarySubtypes = 0),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_selectedHasOneSubtype_returnsFalse() =
+ testScope.runTest {
+ fakeInputMethodRepository.selectedInputMethodSubtypes =
+ listOf(InputMethodModel.Subtype(1, isAuxiliary = false))
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isFalse()
+ }
+
+ @Test
+ fun hasMultipleEnabledImesOrSubtypes_selectedHasTwoSubtypes_returnsTrue() =
+ testScope.runTest {
+ fakeInputMethodRepository.selectedInputMethodSubtypes =
+ listOf(
+ InputMethodModel.Subtype(subtypeId = 1, isAuxiliary = false),
+ InputMethodModel.Subtype(subtypeId = 2, isAuxiliary = false),
+ )
+
+ assertThat(underTest.hasMultipleEnabledImesOrSubtypes(USER_ID)).isTrue()
+ }
+
+ @Test
+ fun showImePicker_shownOnCorrectId() =
+ testScope.runTest {
+ val displayId = 7
+
+ underTest.showInputMethodPicker(displayId, showAuxiliarySubtypes = false)
+
+ assertThat(fakeInputMethodRepository.inputMethodPickerShownDisplayId)
+ .isEqualTo(displayId)
+ }
+
+ private fun createInputMethodWithSubtypes(
+ auxiliarySubtypes: Int,
+ nonAuxiliarySubtypes: Int,
+ ): InputMethodModel {
+ return InputMethodModel(
+ imeId = UUID.randomUUID().toString(),
+ subtypes =
+ List(auxiliarySubtypes + nonAuxiliarySubtypes) {
+ InputMethodModel.Subtype(subtypeId = it, isAuxiliary = it < auxiliarySubtypes)
+ }
+ )
+ }
+
+ companion object {
+ private const val USER_ID = 100
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index e20d3afaca53..ee3e241b5754 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -43,6 +43,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class CameraQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var cameraGestureHelper: CameraGestureHelper
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
index 4ae144c03314..77e0f4ef2c5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
@@ -42,6 +42,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
@Mock private lateinit var context: Context
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 521dea34513e..ca64cec98b2e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -40,6 +40,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var controller: QRCodeScannerController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 6b7d2635ffa4..558e7e6564ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -50,6 +50,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var authController: AuthController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
index ae6c5b7b36b0..a0b85423b9f2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
@@ -40,6 +40,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DevicePostureRepositoryTest : SysuiTestCase() {
private lateinit var underTest: DevicePostureRepository
private lateinit var testScope: TestScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index c01c79dd2601..128b46533a8b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -63,6 +63,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var statusBarStateController: StatusBarStateController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
index ee47c58f4002..5f0f24dcca86 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -46,6 +46,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class TrustRepositoryTest : SysuiTestCase() {
@Mock private lateinit var trustManager: TrustManager
@Captor private lateinit var listener: ArgumentCaptor<TrustManager.TrustListener>
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 6828041eff5a..9368097c0ac9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -45,6 +45,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
+@android.platform.test.annotations.EnabledOnRavenwood
class KeyguardTransitionInteractorTest : SysuiTestCase() {
val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
index e8504563b4ae..4695ea404425 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
@@ -13,6 +13,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class KeyguardRemotePreviewManagerTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index d4dd2ac78e2a..0b80ff8d6ca4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -36,6 +36,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class LockscreenContentViewModelTest : SysuiTestCase() {
private val kosmos: Kosmos = testKosmos()
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
new file mode 100644
index 000000000000..b7b3fdbed955
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.impl.fontscaling.qsFontScalingTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val fontScalingTileConfig = kosmos.qsFontScalingTileConfig
+
+ private val mapper by lazy {
+ FontScalingTileMapper(
+ context.orCreateTestableResources
+ .apply { addOverride(R.drawable.ic_qs_font_scaling, TestStubDrawable()) }
+ .resources,
+ context.theme
+ )
+ }
+
+ @Test
+ fun activeStateMatchesEnabledModel() {
+ val inputModel = FontScalingTileModel
+
+ val outputState = mapper.map(fontScalingTileConfig, inputModel)
+
+ val expectedState = createFontScalingTileState()
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createFontScalingTileState(): QSTileState =
+ QSTileState(
+ {
+ Icon.Loaded(
+ context.getDrawable(
+ R.drawable.ic_qs_font_scaling,
+ )!!,
+ null
+ )
+ },
+ context.getString(R.string.quick_settings_font_scaling_label),
+ QSTileState.ActivationState.ACTIVE,
+ null,
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ context.getString(R.string.quick_settings_font_scaling_label),
+ null,
+ QSTileState.SideViewIcon.Chevron,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt
new file mode 100644
index 000000000000..39bc8a6fa6a2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.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.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.os.UserHandle
+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.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingTileDataInteractorTest : SysuiTestCase() {
+ private val underTest: FontScalingTileDataInteractor = FontScalingTileDataInteractor()
+ private val testUser = UserHandle.of(1)
+
+ @Test
+ fun collectsExactlyOneValue() = runTest {
+ val flowValues by
+ collectValues(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+
+ Truth.assertThat(flowValues.size).isEqualTo(1)
+ }
+
+ @Test
+ fun lastValueIsNotEmpty() = runTest {
+ val flowValue by
+ collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+
+ Truth.assertThat(flowValue).isNotNull()
+ }
+
+ @Test
+ fun isAvailable() = runTest {
+ val availability by collectLastValue(underTest.availability(testUser))
+
+ Truth.assertThat(availability).isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
new file mode 100644
index 000000000000..2384915c8703
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.provider.Settings
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.statusbar.phone.FakeKeyguardStateController
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingUserActionInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+ private val keyguardStateController = FakeKeyguardStateController()
+
+ private lateinit var underTest: FontScalingTileUserActionInteractor
+
+ @Mock private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate
+ @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Mock private lateinit var dialog: SystemUIDialog
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ @Captor private lateinit var argumentCaptor: ArgumentCaptor<Runnable>
+
+ @Before
+ fun setup() {
+ activityStarter = mock<ActivityStarter>()
+ dialogLaunchAnimator = mock<DialogLaunchAnimator>()
+ dialog = mock<SystemUIDialog>()
+ fontScalingDialogDelegate =
+ mock<FontScalingDialogDelegate> { whenever(createDialog()).thenReturn(dialog) }
+ argumentCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+
+ underTest =
+ FontScalingTileUserActionInteractor(
+ kosmos.testScope.coroutineContext,
+ qsTileIntentUserActionHandler,
+ { fontScalingDialogDelegate },
+ keyguardStateController,
+ dialogLaunchAnimator,
+ activityStarter
+ )
+ }
+
+ @Test
+ fun clickTile_screenUnlocked_showDialogAnimationFromView() =
+ kosmos.testScope.runTest {
+ keyguardStateController.isShowing = false
+ val testView = View(context)
+
+ underTest.handleInput(click(FontScalingTileModel, view = testView))
+
+ verify(activityStarter)
+ .executeRunnableDismissingKeyguard(
+ argumentCaptor.capture(),
+ eq(null),
+ eq(true),
+ eq(true),
+ eq(false)
+ )
+ argumentCaptor.value.run()
+ verify(dialogLaunchAnimator).showFromView(any(), eq(testView), nullable(), anyBoolean())
+ }
+
+ @Test
+ fun clickTile_onLockScreen_neverShowDialogAnimationFromView_butShowsDialog() =
+ kosmos.testScope.runTest {
+ keyguardStateController.isShowing = true
+ val testView = View(context)
+
+ underTest.handleInput(click(FontScalingTileModel, view = testView))
+
+ verify(activityStarter)
+ .executeRunnableDismissingKeyguard(
+ argumentCaptor.capture(),
+ eq(null),
+ eq(true),
+ eq(true),
+ eq(false)
+ )
+ argumentCaptor.value.run()
+ verify(dialogLaunchAnimator, never())
+ .showFromView(any(), eq(testView), nullable(), anyBoolean())
+ verify(dialog).show()
+ }
+
+ @Test
+ fun handleLongClick() =
+ kosmos.testScope.runTest {
+ underTest.handleInput(QSTileInputTestKtx.longClick(FontScalingTileModel))
+
+ Truth.assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+ val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+ val actualIntentAction = intentInput.intent.action
+ val expectedIntentAction = Settings.ACTION_TEXT_READING_SETTINGS
+ Truth.assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+ }
+}
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 504ded30a06c..d6d25096ba14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -76,6 +76,8 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -262,6 +264,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
windowController = mock(),
+ deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
)
startable.start()
@@ -518,6 +521,17 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
assertCurrentScene(SceneKey.Lockscreen)
}
+ @Test
+ fun factoryResetProtectionActive_isNotVisible() =
+ testScope.runTest {
+ val isVisible by collectLastValue(sceneContainerViewModel.isVisible)
+ assertThat(isVisible).isTrue()
+
+ kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(isActive = true)
+
+ assertThat(isVisible).isFalse()
+ }
+
/**
* Asserts that the current scene in the view-model matches what's expected.
*
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index b267720971cd..2ad872ca6023 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -40,6 +40,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class SceneContainerRepositoryTest : SysuiTestCase() {
private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
index 7ae501d05fcd..13b5b54510a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
@@ -30,6 +30,7 @@ import org.mockito.Mockito.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class WindowRootViewVisibilityRepositoryTest : SysuiTestCase() {
private val iStatusBarService = mock<IStatusBarService>()
private val executor = FakeExecutor(FakeSystemClock())
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 16cb6231a872..1abbc92a539b 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
@@ -49,6 +49,7 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -112,6 +113,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
authenticationInteractor = dagger.Lazy { authenticationInteractor },
windowController = windowController,
+ deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
index cb83e7c7adbc..bbbee90f2706 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
@@ -30,6 +30,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class BcSmartspaceConfigProviderTest : SysuiTestCase() {
@Mock private lateinit var featureFlags: FeatureFlags
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
index 0b5aea7d8683..089eb43e70a1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
@@ -37,6 +37,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
+@android.platform.test.annotations.EnabledOnRavenwood
class LockscreenPreconditionTest : SysuiTestCase() {
@Mock
private lateinit var deviceProvisionedController: DeviceProvisionedController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt
index bf33010a055b..6616786a3b6c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt
@@ -52,6 +52,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class LockscreenTargetFilterTest : SysuiTestCase() {
@Mock private lateinit var secureSettings: SecureSettings
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
index 8a0400d092c3..f7a8858a2741 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
@@ -38,6 +38,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class RemoteInputRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var remoteInputManager: NotificationRemoteInputManager
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
index 12469ddcafc2..60da53cebc80 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
@@ -34,6 +34,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class RemoteInputInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 6a2e31739c77..4d7d5d3fa664 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -87,21 +87,21 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
}
@Test
- fun updateShadeExpansion() =
+ fun shadeExpansion_goneToShade() =
testScope.runTest {
- val expandFraction by collectLastValue(appearanceViewModel.expandFraction)
- assertThat(expandFraction).isEqualTo(0f)
-
val transitionState =
MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(scene = SceneKey.Lockscreen)
+ ObservableTransitionState.Idle(scene = SceneKey.Gone)
)
sceneInteractor.setTransitionState(transitionState)
+ val expandFraction by collectLastValue(appearanceViewModel.expandFraction)
+ assertThat(expandFraction).isEqualTo(0f)
+
sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
val transitionProgress = MutableStateFlow(0f)
transitionState.value =
ObservableTransitionState.Transition(
- fromScene = SceneKey.Lockscreen,
+ fromScene = SceneKey.Gone,
toScene = SceneKey.Shade,
progress = transitionProgress,
isInitiatedByUserInput = false,
@@ -118,4 +118,49 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
assertThat(expandFraction).isWithin(0.01f).of(1f)
}
+
+ @Test
+ fun shadeExpansion_idleOnLockscreen() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(scene = SceneKey.Lockscreen)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ val expandFraction by collectLastValue(appearanceViewModel.expandFraction)
+ assertThat(expandFraction).isEqualTo(1f)
+ }
+
+ @Test
+ fun shadeExpansion_shadeToQs() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(scene = SceneKey.Shade)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ val expandFraction by collectLastValue(appearanceViewModel.expandFraction)
+ assertThat(expandFraction).isEqualTo(1f)
+
+ sceneInteractor.changeScene(SceneModel(SceneKey.QuickSettings), "reason")
+ val transitionProgress = MutableStateFlow(0f)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Shade,
+ toScene = SceneKey.QuickSettings,
+ progress = transitionProgress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ val steps = 10
+ repeat(steps) { repetition ->
+ val progress = (1f / steps) * (repetition + 1)
+ transitionProgress.value = progress
+ runCurrent()
+ assertThat(expandFraction).isEqualTo(1f)
+ }
+
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason")
+ assertThat(expandFraction).isEqualTo(1f)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
index c7411cd78b78..ffe6e6df6b48 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
@@ -30,6 +30,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
private val kosmos = Kosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
index c669c6f6fb1c..1f6ba29c5fc1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
@@ -33,6 +33,10 @@ public class FakeKeyguardStateController implements KeyguardStateController {
mOccluded = occluded;
}
+ public void setShowing(boolean isShowing) {
+ mShowing = isShowing;
+ }
+
@Override
public boolean isShowing() {
return mShowing;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
index ce00250467f6..18825a29c729 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
@@ -28,6 +28,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class DisabledWifiRepositoryTest : SysuiTestCase() {
private lateinit var underTest: DisabledWifiRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index 7fbbfc77300e..84c728cd9412 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -43,6 +43,7 @@ import org.junit.runner.RunWith
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class WifiInteractorImplTest : SysuiTestCase() {
private lateinit var underTest: WifiInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt
index ebc81be6d4b6..4c8bbe09a688 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt
@@ -45,6 +45,7 @@ import org.mockito.Mockito.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class UserSetupRepositoryTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt
index 26c0f80c53de..7ec0a610b103 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt
@@ -30,6 +30,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class UserSetupInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
index b4a0a37ec9ef..96d1c0de9b66 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
@@ -34,6 +34,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
class BooleanFlowOperatorsTest : SysuiTestCase() {
val kosmos = testKosmos()
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 5d85fbade873..c9e298934acc 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -264,15 +264,18 @@ public interface QSTile {
}
}
- @ProvidesInterface(version = BooleanState.VERSION)
- public static class BooleanState extends State {
+ /**
+ * Distinguished from [BooleanState] for use-case purposes such as allowing null secondary label
+ */
+ @ProvidesInterface(version = AdapterState.VERSION)
+ class AdapterState extends State {
public static final int VERSION = 1;
public boolean value;
public boolean forceExpandIcon;
@Override
public boolean copyTo(State other) {
- final BooleanState o = (BooleanState) other;
+ final AdapterState o = (AdapterState) other;
final boolean changed = super.copyTo(other)
|| o.value != value
|| o.forceExpandIcon != forceExpandIcon;
@@ -291,6 +294,18 @@ public interface QSTile {
@Override
public State copy() {
+ AdapterState state = new AdapterState();
+ copyTo(state);
+ return state;
+ }
+ }
+
+ @ProvidesInterface(version = BooleanState.VERSION)
+ class BooleanState extends AdapterState {
+ public static final int VERSION = 1;
+
+ @Override
+ public State copy() {
BooleanState state = new BooleanState();
copyTo(state);
return state;
diff --git a/packages/SystemUI/res/drawable-nodpi/homecontrols_sq.png b/packages/SystemUI/res/drawable-nodpi/homecontrols_sq.png
new file mode 100644
index 000000000000..00b461b1ef8f
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/homecontrols_sq.png
Binary files differ
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 17719d11345b..7a83070d1806 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -991,7 +991,7 @@
<!-- Component name for Home Panel Dream -->
<string name="config_homePanelDreamComponent" translatable="false">
- @null
+ com.android.systemui/com.android.systemui.dreams.homecontrols.HomeControlsDreamService
</string>
<!--
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fa89fcd17797..33bdca38074e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -903,6 +903,10 @@
Keep it the same as in Launcher-->
<dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen>
+ <!-- Width and height used to filter widgets displayed in the communal widget picker -->
+ <dimen name="communal_widget_picker_desired_width">464dp</dimen>
+ <dimen name="communal_widget_picker_desired_height">307dp</dimen>
+
<!-- The width/height of the unlock icon view on keyguard. -->
<dimen name="keyguard_lock_height">42dp</dimen>
<dimen name="keyguard_lock_padding">20dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 79435885f410..8971859256e5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3314,4 +3314,8 @@
<string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
<!-- Content description for keyboard backlight brightness value [CHAR LIMIT=NONE] -->
<string name="keyboard_backlight_value">Level %1$d of %2$d</string>
+ <!-- Label for home control panel [CHAR LIMIT=30] -->
+ <string name="home_controls_dream_label">Home Controls</string>
+ <!-- Description for home control panel [CHAR LIMIT=50] -->
+ <string name="home_controls_dream_description">Quickly access your home controls as a screensaver</string>
</resources>
diff --git a/packages/SystemUI/res/xml/home_controls_dream_metadata.xml b/packages/SystemUI/res/xml/home_controls_dream_metadata.xml
new file mode 100644
index 000000000000..eb7c79e24b04
--- /dev/null
+++ b/packages/SystemUI/res/xml/home_controls_dream_metadata.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.
+ -->
+<dream xmlns:android="http://schemas.android.com/apk/res/android"
+ android:showClockAndComplications="false"
+ android:previewImage="@drawable/homecontrols_sq"
+ /> \ No newline at end of file
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 326c7ef52fce..4e04af6d00bd 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -53,6 +53,7 @@ android_library {
"SystemUIPluginLib",
"SystemUIUnfoldLib",
"SystemUISharedLib-Keyguard",
+ "WindowManager-Shell-shared",
"tracinglib",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 88b9c020e9a5..a78080fdabf2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -38,7 +38,7 @@ import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.TransitionInfo;
-import com.android.wm.shell.util.CounterRotator;
+import com.android.wm.shell.shared.CounterRotator;
public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub {
private static final String TAG = "RemoteAnimRunnerCompat";
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index d4ac19556b0a..e4d924323bbd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -22,7 +22,7 @@ import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionInfo.Change;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
import java.util.function.Predicate;
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 82410fd39dcd..91e6b6248d34 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -39,7 +39,7 @@ import com.android.systemui.customization.R
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.DisplaySpecific
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags.REGION_SAMPLING
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -47,7 +47,6 @@ import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.core.Logger
-import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.plugins.clocks.ClockMessageBuffers
@@ -92,7 +91,7 @@ constructor(
@Main private val mainExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
private val clockBuffers: ClockMessageBuffers,
- private val featureFlags: FeatureFlags,
+ private val featureFlags: FeatureFlagsClassic,
private val zenModeController: ZenModeController,
) {
var loggers = listOf(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index ad303171d22e..28013c6c8289 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -196,6 +196,9 @@ public class KeyguardClockSwitch extends RelativeLayout {
mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
mStatusArea = findViewById(R.id.keyguard_status_area);
+ } else {
+ removeView(findViewById(R.id.lockscreen_clock_view));
+ removeView(findViewById(R.id.lockscreen_clock_view_large));
}
onConfigChanged();
}
@@ -263,6 +266,9 @@ public class KeyguardClockSwitch extends RelativeLayout {
}
void updateClockTargetRegions() {
+ if (migrateClocksToBlueprint()) {
+ return;
+ }
if (mClock != null) {
if (mSmallClockFrame.isLaidOut()) {
Rect targetRegion = getSmallClockRegion(mSmallClockFrame);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index de7c12db6b11..2db3795cbad7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -246,8 +246,11 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
protected void onInit() {
mKeyguardSliceViewController.init();
- mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
- mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
+ if (!migrateClocksToBlueprint()) {
+ mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
+ mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
+ }
+
if (!mOnlyClock) {
mDumpManager.unregisterDumpable(getClass().getSimpleName()); // unregister previous
@@ -526,13 +529,15 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
*/
void updatePosition(int x, float scale, AnimationProperties props, boolean animate) {
x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x;
+ if (!migrateClocksToBlueprint()) {
+ PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X,
+ x, props, animate);
+ PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
+ scale, props, animate);
+ PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
+ scale, props, animate);
- PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X,
- x, props, animate);
- PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
- scale, props, animate);
- PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
- scale, props, animate);
+ }
if (mStatusArea != null) {
PropertyAnimator.setProperty(mStatusArea, KeyguardStatusAreaView.TRANSLATE_X_AOD,
@@ -550,6 +555,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
return 0;
}
+ if (migrateClocksToBlueprint()) {
+ return 0;
+ }
+
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
// This gets the expected clock bottom if mLargeClockFrame had a top margin, but it's
// top margin only contributed to height and didn't move the top of the view (as this
@@ -581,6 +590,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
boolean isClockTopAligned() {
+ if (migrateClocksToBlueprint()) {
+ return mKeyguardClockInteractor.getClockSize().getValue() == LARGE;
+ }
return mLargeClockFrame.getVisibility() != View.VISIBLE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
index c7e5b645db5f..c23a05128df5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
@@ -61,7 +61,8 @@ constructor(
.observerFlow(userHandle.identifier, SETTING_NAME)
.onStart { emit(Unit) }
.map {
- secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+ secureSettings.getIntForUser(SETTING_NAME, DISABLED, userHandle.identifier) ==
+ ENABLED
}
.distinctUntilChanged()
.flowOn(bgCoroutineContext)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
index 419eada91f87..852579794a27 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
@@ -61,7 +61,8 @@ constructor(
.observerFlow(userHandle.identifier, SETTING_NAME)
.onStart { emit(Unit) }
.map {
- secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+ secureSettings.getIntForUser(SETTING_NAME, DISABLED, userHandle.identifier) ==
+ ENABLED
}
.distinctUntilChanged()
.flowOn(bgCoroutineContext)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 135ab35a4681..4047623a4b2f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -31,6 +31,10 @@ import com.android.systemui.qs.tiles.impl.colorcorrection.domain.ColorCorrection
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionTileDataInteractor
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionUserActionInteractor
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.FontScalingTileMapper
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileDataInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
import com.android.systemui.qs.tiles.impl.inversion.domain.ColorInversionTileMapper
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
@@ -93,6 +97,7 @@ interface QSAccessibilityModule {
companion object {
const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
const val COLOR_INVERSION_TILE_SPEC = "inversion"
+ const val FONT_SCALING_TILE_SPEC = "font_scaling"
@Provides
@IntoMap
@@ -155,5 +160,36 @@ interface QSAccessibilityModule {
stateInteractor,
mapper,
)
+
+ @Provides
+ @IntoMap
+ @StringKey(FONT_SCALING_TILE_SPEC)
+ fun provideFontScalingTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(FONT_SCALING_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.ic_qs_font_scaling,
+ labelRes = R.string.quick_settings_font_scaling_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject FontScaling Tile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(FONT_SCALING_TILE_SPEC)
+ fun provideFontScalingTileViewModel(
+ factory: QSTileViewModelFactory.Static<FontScalingTileModel>,
+ mapper: FontScalingTileMapper,
+ stateInteractor: FontScalingTileDataInteractor,
+ userActionInteractor: FontScalingTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(FONT_SCALING_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 49f34f18b06e..454ed27161a2 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -39,7 +39,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.kotlin.onSubscriberAdded
import com.android.systemui.util.time.SystemClock
import dagger.Binds
import dagger.Module
@@ -54,7 +54,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -355,10 +354,7 @@ constructor(
userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged(),
// Emits a value only when the number of downstream subscribers of this flow
// increases.
- flow.subscriptionCount.pairwise(initialValue = 0).filter { (previous, current)
- ->
- current > previous
- },
+ flow.onSubscriberAdded(),
) { selectedUserId, _ ->
selectedUserId
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
index 70be0f249b33..a742c0e33bc5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
@@ -19,22 +19,15 @@ package com.android.systemui.biometrics.domain.interactor
import android.hardware.biometrics.AuthenticateOptions
import android.hardware.biometrics.IBiometricContextListener
import android.util.Log
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-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.keyguard.WakefulnessLifecycle
+import com.android.systemui.display.data.repository.DeviceStateRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
-import com.android.systemui.unfold.updates.FoldStateProvider
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.catch
@@ -80,15 +73,11 @@ class LogContextInteractorImpl
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- private val foldProvider: FoldStateProvider,
+ deviceStateRepository: DeviceStateRepository,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
udfpsOverlayInteractor: UdfpsOverlayInteractor,
) : LogContextInteractor {
- init {
- applicationScope.launch { foldProvider.start() }
- }
-
override val displayState =
keyguardTransitionInteractor.startedKeyguardTransitionStep.map {
when (it.to) {
@@ -123,32 +112,21 @@ constructor(
.distinctUntilChanged()
override val foldState: Flow<Int> =
- conflatedCallbackFlow {
- val callback =
- object : FoldStateProvider.FoldUpdatesListener {
- override fun onHingeAngleUpdate(angle: Float) {}
-
- override fun onFoldUpdate(@FoldStateProvider.FoldUpdate update: Int) {
- val loggedState =
- when (update) {
- FOLD_UPDATE_FINISH_HALF_OPEN ->
- IBiometricContextListener.FoldState.HALF_OPENED
- FOLD_UPDATE_FINISH_FULL_OPEN ->
- IBiometricContextListener.FoldState.FULLY_OPENED
- FOLD_UPDATE_FINISH_CLOSED ->
- IBiometricContextListener.FoldState.FULLY_CLOSED
- else -> null
- }
- if (loggedState != null) {
- trySendWithFailureLogging(loggedState, TAG)
- }
- }
- }
-
- foldProvider.addCallback(callback)
- trySendWithFailureLogging(IBiometricContextListener.FoldState.UNKNOWN, TAG)
- awaitClose { foldProvider.removeCallback(callback) }
+ deviceStateRepository.state
+ .map {
+ when (it) {
+ DeviceStateRepository.DeviceState.UNFOLDED,
+ DeviceStateRepository.DeviceState.REAR_DISPLAY,
+ DeviceStateRepository.DeviceState.CONCURRENT_DISPLAY ->
+ IBiometricContextListener.FoldState.FULLY_OPENED
+ DeviceStateRepository.DeviceState.FOLDED ->
+ IBiometricContextListener.FoldState.FULLY_CLOSED
+ DeviceStateRepository.DeviceState.HALF_FOLDED ->
+ IBiometricContextListener.FoldState.HALF_OPENED
+ else -> IBiometricContextListener.FoldState.UNKNOWN
+ }
}
+ .distinctUntilChanged()
.shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
override fun addBiometricContextListener(listener: IBiometricContextListener): Job {
@@ -178,6 +156,3 @@ constructor(
private const val TAG = "ContextRepositoryImpl"
}
}
-
-private val WakefulnessLifecycle.isAwake: Boolean
- get() = wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
index 7265c0c1cb94..d849b3a44519 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
@@ -21,8 +21,6 @@ import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
/** Provides access to bouncer-related application state. */
@SysUISingleton
@@ -31,15 +29,10 @@ class BouncerRepository
constructor(
private val flags: FeatureFlagsClassic,
) {
- private val _message = MutableStateFlow<String?>(null)
/** The user-facing message to show in the bouncer. */
- val message: StateFlow<String?> = _message.asStateFlow()
+ val message = MutableStateFlow<String?>(null)
/** Whether the user switcher should be displayed within the bouncer UI on large screens. */
val isUserSwitcherVisible: Boolean
get() = flags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)
-
- fun setMessage(message: String?) {
- _message.value = message
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index c8ce245e48bf..d8be1afc4dd6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -120,7 +120,7 @@ constructor(
}
fun setMessage(message: String?) {
- repository.setMessage(message)
+ repository.message.value = message
}
/**
@@ -129,13 +129,13 @@ constructor(
*/
fun resetMessage() {
applicationScope.launch {
- repository.setMessage(promptMessage(authenticationInteractor.getAuthenticationMethod()))
+ setMessage(promptMessage(authenticationInteractor.getAuthenticationMethod()))
}
}
/** Removes the user-facing message. */
fun clearMessage() {
- repository.setMessage(null)
+ setMessage(null)
}
/**
@@ -196,7 +196,7 @@ constructor(
* message without having the attempt trigger lockout.
*/
private suspend fun showWrongInputMessage() {
- repository.setMessage(wrongInputMessage(authenticationInteractor.getAuthenticationMethod()))
+ setMessage(wrongInputMessage(authenticationInteractor.getAuthenticationMethod()))
}
/** Notifies that the input method editor (software keyboard) has been hidden by the user. */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 4d686a1ba0d4..4466cbbe05be 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -34,7 +34,9 @@ import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import com.android.systemui.user.ui.viewmodel.UserViewModel
@@ -66,8 +68,10 @@ class BouncerViewModel(
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
private val bouncerInteractor: BouncerInteractor,
+ private val inputMethodInteractor: InputMethodInteractor,
private val simBouncerInteractor: SimBouncerInteractor,
private val authenticationInteractor: AuthenticationInteractor,
+ private val selectedUserInteractor: SelectedUserInteractor,
flags: SceneContainerFlags,
selectedUser: Flow<UserViewModel>,
users: Flow<List<UserViewModel>>,
@@ -346,8 +350,10 @@ class BouncerViewModel(
is AuthenticationMethodModel.Password ->
PasswordBouncerViewModel(
viewModelScope = newViewModelScope,
- interactor = bouncerInteractor,
isInputEnabled = isInputEnabled,
+ interactor = bouncerInteractor,
+ inputMethodInteractor = inputMethodInteractor,
+ selectedUserInteractor = selectedUserInteractor,
)
is AuthenticationMethodModel.Pattern ->
PatternBouncerViewModel(
@@ -467,11 +473,13 @@ object BouncerViewModelModule {
@Application applicationScope: CoroutineScope,
@Main mainDispatcher: CoroutineDispatcher,
bouncerInteractor: BouncerInteractor,
+ imeInteractor: InputMethodInteractor,
simBouncerInteractor: SimBouncerInteractor,
+ actionButtonInteractor: BouncerActionButtonInteractor,
authenticationInteractor: AuthenticationInteractor,
+ selectedUserInteractor: SelectedUserInteractor,
flags: SceneContainerFlags,
userSwitcherViewModel: UserSwitcherViewModel,
- actionButtonInteractor: BouncerActionButtonInteractor,
clock: SystemClock,
devicePolicyManager: DevicePolicyManager,
): BouncerViewModel {
@@ -480,8 +488,10 @@ object BouncerViewModelModule {
applicationScope = applicationScope,
mainDispatcher = mainDispatcher,
bouncerInteractor = bouncerInteractor,
+ inputMethodInteractor = imeInteractor,
simBouncerInteractor = simBouncerInteractor,
authenticationInteractor = authenticationInteractor,
+ selectedUserInteractor = selectedUserInteractor,
flags = flags,
selectedUser = userSwitcherViewModel.selectedUser,
users = userSwitcherViewModel.users,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 5c9c997db7e4..1c8b84d82a56 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -16,23 +16,32 @@
package com.android.systemui.bouncer.ui.viewmodel
+import androidx.annotation.VisibleForTesting
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
import com.android.systemui.res.R
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.onSubscriberAdded
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** Holds UI state and handles user input for the password bouncer UI. */
class PasswordBouncerViewModel(
viewModelScope: CoroutineScope,
- interactor: BouncerInteractor,
isInputEnabled: StateFlow<Boolean>,
+ interactor: BouncerInteractor,
+ private val inputMethodInteractor: InputMethodInteractor,
+ private val selectedUserInteractor: SelectedUserInteractor,
) :
AuthMethodBouncerViewModel(
viewModelScope = viewModelScope,
@@ -49,6 +58,9 @@ class PasswordBouncerViewModel(
override val lockoutMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
+ /** Informs the UI whether the input method switcher button should be visible. */
+ val isImeSwitcherButtonVisible: StateFlow<Boolean> = imeSwitcherRefreshingFlow()
+
/** Whether the text field element currently has focus. */
private val isTextFieldFocused = MutableStateFlow(false)
@@ -87,6 +99,13 @@ class PasswordBouncerViewModel(
_password.value = newPassword
}
+ /** Notifies that the user clicked the button to change the input method. */
+ fun onImeSwitcherButtonClicked(displayId: Int) {
+ viewModelScope.launch {
+ inputMethodInteractor.showInputMethodPicker(displayId, showAuxiliarySubtypes = false)
+ }
+ }
+
/** Notifies that the user has pressed the key for attempting to authenticate the password. */
fun onAuthenticateKeyPressed() {
if (_password.value.isNotEmpty()) {
@@ -103,4 +122,35 @@ class PasswordBouncerViewModel(
fun onTextFieldFocusChanged(isFocused: Boolean) {
isTextFieldFocused.value = isFocused
}
+
+ /**
+ * Whether the input method switcher button should be displayed in the password bouncer UI. The
+ * value may be stale at the moment of subscription to this flow, but it is guaranteed to be
+ * shortly updated with a fresh value.
+ *
+ * Note: Each added subscription triggers an IPC call in the background, so this should only be
+ * subscribed to by the UI once in its lifecycle (i.e. when the bouncer is shown).
+ */
+ private fun imeSwitcherRefreshingFlow(): StateFlow<Boolean> {
+ val isImeSwitcherButtonVisible = MutableStateFlow(value = false)
+ viewModelScope.launch {
+ // Re-fetch the currently-enabled IMEs whenever the selected user changes, and whenever
+ // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
+ combine(
+ // InputMethodManagerService sometimes takes some time to update its internal
+ // state when the selected user changes. As a workaround, delay fetching the IME
+ // info.
+ selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) },
+ isImeSwitcherButtonVisible.onSubscriberAdded()
+ ) { selectedUserId, _ ->
+ inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
+ }
+ .collect { isImeSwitcherButtonVisible.value = it }
+ }
+ return isImeSwitcherButtonVisible.asStateFlow()
+ }
+
+ companion object {
+ @VisibleForTesting val DELAY_TO_FETCH_IMES = 300.milliseconds
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 779446d3cd37..3b727d2a7ade 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -97,7 +97,7 @@ interface CommunalWidgetDao {
fun getWidgets(): Flow<Map<CommunalItemRank, CommunalWidgetItem>>
@Query("SELECT * FROM communal_widget_table WHERE widget_id = :id")
- fun getWidgetByIdNow(id: Int): CommunalWidgetItem
+ fun getWidgetByIdNow(id: Int): CommunalWidgetItem?
@Delete fun deleteWidgets(vararg widgets: CommunalWidgetItem)
@@ -120,7 +120,9 @@ interface CommunalWidgetDao {
fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
widgetIdToPriorityMap.forEach { (id, priority) ->
val widget = getWidgetByIdNow(id)
- updateItemRank(widget.itemId, priority)
+ if (widget != null) {
+ updateItemRank(widget.itemId, priority)
+ }
}
}
@@ -134,9 +136,13 @@ interface CommunalWidgetDao {
}
@Transaction
- fun deleteWidgetById(widgetId: Int) {
- val widget = getWidgetByIdNow(widgetId)
+ fun deleteWidgetById(widgetId: Int): Boolean {
+ val widget =
+ getWidgetByIdNow(widgetId) ?: // no entry to delete from db
+ return false
+
deleteItemRankById(widget.itemId)
deleteWidgets(widget)
+ return true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
index cf2e33ce1df5..c46f0d1cfcb6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
@@ -16,15 +16,34 @@
package com.android.systemui.communal.data.model
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
/** Data model of media on the communal hub. */
data class CommunalMediaModel(
val hasAnyMediaOrRecommendation: Boolean,
val createdTimestampMillis: Long = 0L,
-) {
+) : Diffable<CommunalMediaModel> {
companion object {
val INACTIVE =
CommunalMediaModel(
hasAnyMediaOrRecommendation = false,
)
}
+
+ override fun logDiffs(prevVal: CommunalMediaModel, row: TableRowLogger) {
+ if (hasAnyMediaOrRecommendation != prevVal.hasAnyMediaOrRecommendation) {
+ row.logChange(
+ columnName = "isMediaActive",
+ value = hasAnyMediaOrRecommendation,
+ )
+ }
+
+ if (createdTimestampMillis != prevVal.createdTimestampMillis) {
+ row.logChange(
+ columnName = "mediaCreationTimestamp",
+ value = createdTimestampMillis.toString(),
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e8a561b37d20..2b66491613ab 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -18,6 +18,9 @@ package com.android.systemui.communal.data.repository
import com.android.systemui.communal.data.model.CommunalMediaModel
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import javax.inject.Inject
@@ -34,6 +37,7 @@ class CommunalMediaRepositoryImpl
@Inject
constructor(
private val mediaDataManager: MediaDataManager,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalMediaRepository {
private val mediaDataListener =
@@ -61,7 +65,12 @@ constructor(
private val _mediaModel: MutableStateFlow<CommunalMediaModel> =
MutableStateFlow(CommunalMediaModel.INACTIVE)
- override val mediaModel: Flow<CommunalMediaModel> = _mediaModel
+ override val mediaModel: Flow<CommunalMediaModel> =
+ _mediaModel.logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ initialValue = CommunalMediaModel.INACTIVE,
+ )
private fun updateMediaModel(data: MediaData? = null) {
if (mediaDataManager.hasAnyMediaOrRecommendation()) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index c2ea2e93ce58..85aeb4d4f12f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -21,6 +21,12 @@ import android.content.SharedPreferences
import android.content.pm.UserInfo
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.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserFileManagerExt.observeSharedPreferences
import com.android.systemui.user.data.repository.UserRepository
@@ -59,11 +65,21 @@ constructor(
@Background private val bgDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
private val userFileManager: UserFileManager,
+ @CommunalLog logBuffer: LogBuffer,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalPrefsRepository {
+ private val logger = Logger(logBuffer, "CommunalPrefsRepositoryImpl")
+
override val isCtaDismissed: Flow<Boolean> =
userRepository.selectedUserInfo
.flatMapLatest(::observeCtaDismissState)
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isCtaDismissed",
+ initialValue = false,
+ )
.stateIn(
scope = backgroundScope,
started = SharingStarted.WhileSubscribed(),
@@ -76,6 +92,8 @@ constructor(
.edit()
.putBoolean(CTA_DISMISSED_STATE, true)
.apply()
+
+ logger.i("Dismissed CTA tile")
}
private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
index 046aaaa33342..4c06e97c89b0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
@@ -25,6 +25,9 @@ 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.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -66,6 +69,7 @@ constructor(
private val userRepository: UserRepository,
private val secureSettings: SecureSettings,
@CommunalLog logBuffer: LogBuffer,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalTutorialRepository {
companion object {
@@ -94,6 +98,12 @@ constructor(
settingsState
.map { it.hubModeTutorialState }
.filterNotNull()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "tutorialSettingState",
+ initialValue = HUB_MODE_TUTORIAL_NOT_STARTED,
+ )
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index f36547b01802..2ac9d053f8d0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -54,8 +54,11 @@ interface CommunalWidgetRepository {
configurator: WidgetConfigurator? = null
) {}
- /** Delete a widget by id from app widget service and the database. */
- fun deleteWidget(widgetId: Int) {}
+ /** Delete a widget by id from the database. */
+ fun deleteWidgetFromDb(widgetId: Int) {}
+
+ /** Delete a widget by id from app widget host. */
+ fun deleteWidgetFromHost(widgetId: Int) {}
/**
* Update the order of widgets in the database.
@@ -143,9 +146,18 @@ constructor(
}
}
- override fun deleteWidget(widgetId: Int) {
+ override fun deleteWidgetFromDb(widgetId: Int) {
+ bgScope.launch {
+ if (communalWidgetDao.deleteWidgetById(widgetId)) {
+ logger.i("Deleted widget with id $widgetId from DB .")
+ } else {
+ logger.w("Widget with id $widgetId cannot be deleted from DB.")
+ }
+ }
+ }
+
+ override fun deleteWidgetFromHost(widgetId: Int) {
bgScope.launch {
- communalWidgetDao.deleteWidgetById(widgetId)
appWidgetHost.deleteAppWidgetId(widgetId)
logger.i("Deleted widget with id $widgetId.")
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
index ab0a2d0d232f..d7f163bad2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
@@ -26,6 +26,7 @@ import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.WidgetInteractionHandler
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.dagger.CommunalLog
@@ -35,6 +36,7 @@ import dagger.Module
import dagger.Provides
import java.util.Optional
import javax.inject.Named
+import kotlinx.coroutines.CoroutineScope
@Module
interface CommunalWidgetRepositoryModule {
@@ -52,10 +54,19 @@ interface CommunalWidgetRepositoryModule {
@Provides
fun provideCommunalAppWidgetHost(
@Application context: Context,
+ @Background backgroundScope: CoroutineScope,
interactionHandler: WidgetInteractionHandler,
@Main looper: Looper,
+ @CommunalLog logBuffer: LogBuffer,
): CommunalAppWidgetHost {
- return CommunalAppWidgetHost(context, APP_WIDGET_HOST_ID, interactionHandler, looper)
+ return CommunalAppWidgetHost(
+ context,
+ backgroundScope,
+ APP_WIDGET_HOST_ID,
+ interactionHandler,
+ looper,
+ logBuffer,
+ )
}
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 80fee640974d..75a27a278116 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -33,16 +33,25 @@ import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.BooleanFlowOperators.and
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
@@ -50,6 +59,8 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
/** Encapsulates business-logic related to communal mode. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -57,6 +68,7 @@ import kotlinx.coroutines.flow.map
class CommunalInteractor
@Inject
constructor(
+ @Application applicationScope: CoroutineScope,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
private val communalPrefsRepository: CommunalPrefsRepository,
@@ -65,8 +77,12 @@ constructor(
userRepository: UserRepository,
keyguardInteractor: KeyguardInteractor,
private val appWidgetHost: CommunalAppWidgetHost,
- private val editWidgetsActivityStarter: EditWidgetsActivityStarter
+ private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
+ @CommunalLog logBuffer: LogBuffer,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
+ private val logger = Logger(logBuffer, "CommunalInteractor")
+
private val _editModeOpen = MutableStateFlow(false)
/** Whether edit mode is currently open. */
@@ -85,6 +101,22 @@ constructor(
or(keyguardInteractor.isKeyguardVisible, keyguardInteractor.isDreaming)
)
.distinctUntilChanged()
+ .onEach { available ->
+ logger.i({ "Communal is ${if (bool1) "" else "un"}available" }) {
+ bool1 = available
+ }
+ }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isCommunalAvailable",
+ initialValue = false,
+ )
+ .shareIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through
@@ -129,11 +161,36 @@ constructor(
.distinctUntilChanged()
/**
- * Flow that emits a boolean if the communal UI is showing, ie. the [desiredScene] is the
- * [CommunalSceneKey.Communal].
+ * Flow that emits a boolean if the communal UI is the target scene, ie. the [desiredScene] is
+ * the [CommunalSceneKey.Communal].
+ *
+ * This will be true as soon as the desired scene is set programmatically or at whatever point
+ * during a fling that SceneTransitionLayout determines that the end state will be the communal
+ * scene. The value also does not change while flinging away until the target scene is no longer
+ * communal.
+ *
+ * If you need a flow that is only true when communal is fully showing and not in transition,
+ * use [isIdleOnCommunal].
*/
+ // TODO(b/323215860): rename to something more appropriate after cleaning up usages
val isCommunalShowing: Flow<Boolean> =
- communalRepository.desiredScene.map { it == CommunalSceneKey.Communal }
+ communalRepository.desiredScene
+ .map { it == CommunalSceneKey.Communal }
+ .distinctUntilChanged()
+ .onEach { showing ->
+ logger.i({ "Communal is ${if (bool1) "showing" else "gone"}" }) { bool1 = showing }
+ }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isCommunalShowing",
+ initialValue = false,
+ )
+ .shareIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
/**
* Flow that emits a boolean if the communal UI is fully visible and not in transition.
@@ -146,6 +203,16 @@ constructor(
it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Communal
}
+ /**
+ * Flow that emits a boolean if any portion of the communal UI is visible at all.
+ *
+ * This flow will be true during any transition and when idle on the communal scene.
+ */
+ val isCommunalVisible: Flow<Boolean> =
+ communalRepository.transitionState.map {
+ !(it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Blank)
+ }
+
/** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
fun onSceneChanged(newScene: CommunalSceneKey) {
communalRepository.setDesiredScene(newScene)
@@ -170,9 +237,15 @@ constructor(
configurator: WidgetConfigurator?,
) = widgetRepository.addWidget(componentName, priority, configurator)
- /** Delete a widget by id. */
- fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
+ /**
+ * Delete a widget by id from the database. [CommunalAppWidgetHostStartable] invokes this
+ * function to manage the deletion from the database for uninstalled or user-deleted widgets,
+ * following the removal of a widget from the host.
+ */
+ fun deleteWidgetFromDb(id: Int) = widgetRepository.deleteWidgetFromDb(id)
+ /** Delete a widget by id from AppWidgetHost. Called when user deletes a widget from the hub */
+ fun deleteWidgetFromHost(id: Int) = widgetRepository.deleteWidgetFromHost(id)
/**
* Reorder the widgets.
*
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
index 309c84e4e955..1404ee20cb72 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -22,6 +22,9 @@ import com.android.systemui.communal.data.repository.CommunalTutorialRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,6 +52,7 @@ constructor(
keyguardInteractor: KeyguardInteractor,
private val communalRepository: CommunalRepository,
communalInteractor: CommunalInteractor,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
/** An observable for whether the tutorial is available. */
val isTutorialAvailable: StateFlow<Boolean> =
@@ -61,6 +65,12 @@ constructor(
isKeyguardVisible &&
tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
}
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isTutorialAvailable",
+ initialValue = false,
+ )
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index ebcfb8bb8209..69d55815fe01 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -21,6 +21,9 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.dagger.MediaModule
import javax.inject.Inject
@@ -29,6 +32,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
/** The view model for communal hub in edit mode. */
@SysUISingleton
@@ -38,21 +42,27 @@ constructor(
private val communalInteractor: CommunalInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
private val uiEventLogger: UiEventLogger,
+ @CommunalLog logBuffer: LogBuffer,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
+
+ private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
+
override val isEditMode = true
// Only widgets are editable. The CTA tile comes last in the list and remains visible.
override val communalContent: Flow<List<CommunalContentModel>> =
- communalInteractor.widgetContent.map { widgets ->
- widgets + listOf(CommunalContentModel.CtaTileInEditMode())
- }
+ communalInteractor.widgetContent
+ .map { widgets -> widgets + listOf(CommunalContentModel.CtaTileInEditMode()) }
+ .onEach { models ->
+ logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
+ }
private val _reorderingWidgets = MutableStateFlow(false)
override val reorderingWidgets: StateFlow<Boolean>
get() = _reorderingWidgets
- override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
+ override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidgetFromHost(id)
override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index d7a3705248ff..0c12841aa42b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -21,6 +21,9 @@ import com.android.systemui.communal.domain.interactor.CommunalTutorialInteracto
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.controls.ui.MediaHostState
@@ -37,6 +40,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
/** The default view model used for showing the communal hub. */
@@ -48,22 +52,30 @@ constructor(
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
+ @CommunalLog logBuffer: LogBuffer,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
+
+ private val logger = Logger(logBuffer, "CommunalViewModel")
+
@OptIn(ExperimentalCoroutinesApi::class)
override val communalContent: Flow<List<CommunalContentModel>> =
- tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode ->
- if (isTutorialMode) {
- return@flatMapLatest flowOf(communalInteractor.tutorialContent)
+ tutorialInteractor.isTutorialAvailable
+ .flatMapLatest { isTutorialMode ->
+ if (isTutorialMode) {
+ return@flatMapLatest flowOf(communalInteractor.tutorialContent)
+ }
+ combine(
+ communalInteractor.ongoingContent,
+ communalInteractor.widgetContent,
+ communalInteractor.ctaTileContent,
+ ) { ongoing, widgets, ctaTile,
+ ->
+ ongoing + widgets + ctaTile
+ }
}
- combine(
- communalInteractor.ongoingContent,
- communalInteractor.widgetContent,
- communalInteractor.ctaTileContent,
- ) { ongoing, widgets, ctaTile,
- ->
- ongoing + widgets + ctaTile
+ .onEach { models ->
+ logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
}
- }
private val _isPopupOnDismissCtaShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isPopupOnDismissCtaShowing: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
index 61db02639788..5f1d89e079a7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -22,14 +22,31 @@ import android.appwidget.AppWidgetProviderInfo
import android.content.Context
import android.os.Looper
import android.widget.RemoteViews
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.launch
/** Communal app widget host that creates a [CommunalAppWidgetHostView]. */
class CommunalAppWidgetHost(
context: Context,
+ private val backgroundScope: CoroutineScope,
hostId: Int,
interactionHandler: RemoteViews.InteractionHandler,
- looper: Looper
+ looper: Looper,
+ logBuffer: LogBuffer,
) : AppWidgetHost(context, hostId, interactionHandler, looper) {
+
+ private val logger = Logger(logBuffer, TAG)
+
+ private val _appWidgetIdToRemove = MutableSharedFlow<Int>()
+
+ /** App widget ids that have been removed and no longer available. */
+ val appWidgetIdToRemove: SharedFlow<Int> = _appWidgetIdToRemove.asSharedFlow()
+
override fun onCreateView(
context: Context,
appWidgetId: Int,
@@ -52,4 +69,15 @@ class CommunalAppWidgetHost(
// `createView`, but we are sure that the hostView is `CommunalAppWidgetHostView`
return createView(context, appWidgetId, appWidget) as CommunalAppWidgetHostView
}
+
+ override fun onAppWidgetRemoved(appWidgetId: Int) {
+ backgroundScope.launch {
+ logger.i({ "App widget removed from system: $int1" }) { int1 = appWidgetId }
+ _appWidgetIdToRemove.emit(appWidgetId)
+ }
+ }
+
+ companion object {
+ private const val TAG = "CommunalAppWidgetHost"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
index 586df32e6561..6fd0fbe80fd7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -40,6 +40,7 @@ constructor(
@Background private val bgScope: CoroutineScope,
@Main private val uiDispatcher: CoroutineDispatcher
) : CoreStartable {
+
override fun start() {
or(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
// Only trigger updates on state changes, ignoring the initial false value.
@@ -47,6 +48,10 @@ constructor(
.filter { (previous, new) -> previous != new }
.onEach { (_, shouldListen) -> updateAppWidgetHostActive(shouldListen) }
.launchIn(bgScope)
+
+ appWidgetHost.appWidgetIdToRemove
+ .onEach { appWidgetId -> communalInteractor.deleteWidgetFromDb(appWidgetId) }
+ .launchIn(bgScope)
}
private suspend fun updateAppWidgetHostActive(active: Boolean) =
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 54c709d4f053..92e8153840eb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -32,6 +32,10 @@ import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.res.R
import javax.inject.Inject
/** An Activity for editing the widgets that appear in hub mode. */
@@ -41,16 +45,21 @@ constructor(
private val communalViewModel: CommunalEditModeViewModel,
private var windowManagerService: IWindowManager? = null,
private val uiEventLogger: UiEventLogger,
- private val widgetConfiguratorFactory: WidgetConfigurationController.Factory
+ private val widgetConfiguratorFactory: WidgetConfigurationController.Factory,
+ @CommunalLog logBuffer: LogBuffer,
) : ComponentActivity() {
companion object {
private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
- private const val EXTRA_FILTER_STRATEGY = "filter_strategy"
- private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1
+ private const val EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width"
+
+ private const val EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height"
+
private const val TAG = "EditWidgetsActivity"
const val EXTRA_PRESELECTED_KEY = "preselected_key"
}
+ private val logger = Logger(logBuffer, "EditWidgetsActivity")
+
private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
@@ -111,11 +120,19 @@ constructor(
?.let { packageName ->
try {
addWidgetActivityLauncher.launch(
- Intent(Intent.ACTION_PICK).also {
- it.setPackage(packageName)
- it.putExtra(
- EXTRA_FILTER_STRATEGY,
- FILTER_STRATEGY_GLANCEABLE_HUB
+ Intent(Intent.ACTION_PICK).apply {
+ setPackage(packageName)
+ putExtra(
+ EXTRA_DESIRED_WIDGET_WIDTH,
+ resources.getDimensionPixelSize(
+ R.dimen.communal_widget_picker_desired_width
+ )
+ )
+ putExtra(
+ EXTRA_DESIRED_WIDGET_HEIGHT,
+ resources.getDimensionPixelSize(
+ R.dimen.communal_widget_picker_desired_height
+ )
)
}
)
@@ -146,11 +163,15 @@ constructor(
override fun onStart() {
super.onStart()
+
+ logger.i("Starting the communal widget editor activity")
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
}
override fun onStop() {
super.onStop()
+
+ logger.i("Stopping the communal widget editor activity")
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 9504cfcdbe3c..5ee2045865a6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -31,6 +31,7 @@ import com.android.systemui.controls.dagger.StartControlsStartableModule
import com.android.systemui.dagger.qualifiers.PerUser
import com.android.systemui.dreams.AssistantAttentionMonitor
import com.android.systemui.dreams.DreamMonitor
+import com.android.systemui.dreams.homecontrols.HomeControlsDreamStartable
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.keyboard.KeyboardUI
import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable
@@ -331,4 +332,9 @@ abstract class SystemUICoreStartableModule {
abstract fun bindCommunalAppWidgetHostStartable(
impl: CommunalAppWidgetHostStartable
): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(HomeControlsDreamStartable::class)
+ abstract fun bindHomeControlsDreamStartable(impl: HomeControlsDreamStartable): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 2587e2da2172..efcbd47b67b4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -57,6 +57,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagDependenciesModule;
import com.android.systemui.flags.FlagsModule;
+import com.android.systemui.inputmethod.InputMethodModule;
import com.android.systemui.keyboard.KeyboardModule;
import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule;
import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule;
@@ -193,6 +194,7 @@ import javax.inject.Named;
FlagsModule.class,
FlagDependenciesModule.class,
FooterActionsModule.class,
+ InputMethodModule.class,
KeyEventRepositoryModule.class,
KeyboardModule.class,
KeyguardBlueprintModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 0656933804f3..ba74742fc47f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.dreams.dagger;
import android.annotation.Nullable;
+import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -30,12 +31,18 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayNotificationCountProvider;
import com.android.systemui.dreams.DreamOverlayService;
import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
+import com.android.systemui.dreams.homecontrols.DreamActivityProvider;
+import com.android.systemui.dreams.homecontrols.DreamActivityProviderImpl;
+import com.android.systemui.dreams.homecontrols.HomeControlsDreamService;
import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule;
import com.android.systemui.res.R;
import com.android.systemui.touch.TouchInsetManager;
+import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -88,6 +95,15 @@ public interface DreamModule {
}
/**
+ * Provides Home Controls Dream Service
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(HomeControlsDreamService.class)
+ Service bindHomeControlsDreamService(
+ HomeControlsDreamService service);
+
+ /**
* Provides a touch inset manager for dreams.
*/
@Provides
@@ -151,4 +167,9 @@ public interface DreamModule {
static String providesDreamOverlayWindowTitle(@Main Resources resources) {
return resources.getString(R.string.app_label);
}
+
+ /** Provides activity for dream service */
+ @Binds
+ DreamActivityProvider bindActivityProvider(DreamActivityProviderImpl impl);
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt
new file mode 100644
index 000000000000..b35b7f5debb3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.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.systemui.dreams.homecontrols
+
+import android.app.Activity
+import android.service.dreams.DreamService
+
+fun interface DreamActivityProvider {
+ /**
+ * Provides abstraction for getting the activity associated with a dream service, so that the
+ * activity can be mocked in tests.
+ */
+ fun getActivity(dreamService: DreamService): Activity?
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
new file mode 100644
index 000000000000..0854e939645b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.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.dreams.homecontrols
+
+import android.app.Activity
+import android.service.dreams.DreamService
+import javax.inject.Inject
+
+class DreamActivityProviderImpl @Inject constructor() : DreamActivityProvider {
+ override fun getActivity(dreamService: DreamService): Activity {
+ return dreamService.activity
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
new file mode 100644
index 000000000000..e04a5052199c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -0,0 +1,88 @@
+/*
+ * 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
+
+import android.content.Intent
+import android.service.controls.ControlsProviderService
+import android.service.dreams.DreamService
+import android.window.TaskFragmentInfo
+import com.android.systemui.controls.settings.ControlsSettingsRepository
+import com.android.systemui.dreams.DreamLogger
+import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.dagger.DreamLog
+import javax.inject.Inject
+
+class HomeControlsDreamService
+@Inject
+constructor(
+ private val controlsSettingsRepository: ControlsSettingsRepository,
+ private val taskFragmentFactory: TaskFragmentComponent.Factory,
+ private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
+ private val dreamActivityProvider: DreamActivityProvider,
+ @DreamLog logBuffer: LogBuffer
+) : DreamService() {
+ private lateinit var taskFragmentComponent: TaskFragmentComponent
+
+ private val logger = DreamLogger(logBuffer, "HomeControlsDreamService")
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ val activity = dreamActivityProvider.getActivity(this)
+ if (activity == null) {
+ finish()
+ return
+ }
+ taskFragmentComponent =
+ taskFragmentFactory
+ .create(
+ activity = activity,
+ onCreateCallback = this::onTaskFragmentCreated,
+ onInfoChangedCallback = this::onTaskFragmentInfoChanged,
+ hide = { finish() }
+ )
+ .apply { createTaskFragment() }
+ }
+
+ private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) {
+ if (taskFragmentInfo.isEmpty) {
+ logger.d("Finishing dream due to TaskFragment being empty")
+ finish()
+ }
+ }
+
+ private fun onTaskFragmentCreated(taskFragmentInfo: TaskFragmentInfo) {
+ val setting = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
+ val componentName = homeControlsComponentInteractor.panelComponent.value
+ logger.d("Starting embedding $componentName")
+ val intent =
+ Intent().apply {
+ component = componentName
+ putExtra(ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, setting)
+ putExtra(
+ ControlsProviderService.EXTRA_CONTROLS_SURFACE,
+ ControlsProviderService.CONTROLS_SURFACE_DREAM
+ )
+ }
+ taskFragmentComponent.startActivityInTaskFragment(intent)
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ taskFragmentComponent.destroy()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
new file mode 100644
index 000000000000..6cd94c623ff7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
@@ -0,0 +1,63 @@
+/*
+ * 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
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.service.controls.flags.Flags.homePanelDream
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+class HomeControlsDreamStartable
+@Inject
+constructor(
+ private val context: Context,
+ private val packageManager: PackageManager,
+ private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
+ @Background private val bgScope: CoroutineScope,
+) : CoreStartable {
+
+ private val componentName = ComponentName(context, HomeControlsDreamService::class.java)
+
+ override fun start() {
+ if (!homePanelDream()) return
+ bgScope.launch {
+ homeControlsComponentInteractor.panelComponent.collect { selectedPanelComponent ->
+ setEnableHomeControlPanel(selectedPanelComponent != null)
+ }
+ }
+ }
+
+ private fun setEnableHomeControlPanel(enabled: Boolean) {
+ val packageState =
+ if (enabled) {
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ } else {
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ }
+ packageManager.setComponentEnabledSetting(
+ componentName,
+ packageState,
+ PackageManager.DONT_KILL_APP
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
new file mode 100644
index 000000000000..6f7dcb173156
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
@@ -0,0 +1,166 @@
+/*
+ * 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
+
+import android.app.Activity
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.content.Intent
+import android.graphics.Rect
+import android.os.Binder
+import android.window.TaskFragmentCreationParams
+import android.window.TaskFragmentInfo
+import android.window.TaskFragmentOperation
+import android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT
+import android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK
+import android.window.TaskFragmentOrganizer
+import android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE
+import android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE
+import android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN
+import android.window.TaskFragmentTransaction
+import android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK
+import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED
+import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR
+import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED
+import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED
+import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED
+import android.window.WindowContainerTransaction
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.concurrency.DelayableExecutor
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+typealias FragmentInfoCallback = (TaskFragmentInfo) -> Unit
+
+/** Wrapper around TaskFragmentOrganizer for managing a task fragment within an activity */
+class TaskFragmentComponent
+@AssistedInject
+constructor(
+ @Assisted private val activity: Activity,
+ @Assisted("onCreateCallback") private val onCreateCallback: FragmentInfoCallback,
+ @Assisted("onInfoChangedCallback") private val onInfoChangedCallback: FragmentInfoCallback,
+ @Assisted private val hide: () -> Unit,
+ @Main private val executor: DelayableExecutor,
+) {
+
+ @AssistedFactory
+ fun interface Factory {
+ fun create(
+ activity: Activity,
+ @Assisted("onCreateCallback") onCreateCallback: FragmentInfoCallback,
+ @Assisted("onInfoChangedCallback") onInfoChangedCallback: FragmentInfoCallback,
+ hide: () -> Unit
+ ): TaskFragmentComponent
+ }
+
+ private val fragmentToken = Binder()
+ private val organizer: TaskFragmentOrganizer =
+ object : TaskFragmentOrganizer(executor) {
+
+ override fun onTransactionReady(transaction: TaskFragmentTransaction) {
+ handleTransactionReady(transaction)
+ }
+ }
+ .apply { registerOrganizer(true /* isSystemOrganizer */) }
+
+ private fun handleTransactionReady(transaction: TaskFragmentTransaction) {
+ val resultT = WindowContainerTransaction()
+
+ for (change in transaction.changes) {
+ change.taskFragmentInfo?.let { taskFragmentInfo ->
+ if (taskFragmentInfo.fragmentToken == fragmentToken) {
+ when (change.type) {
+ TYPE_TASK_FRAGMENT_APPEARED -> {
+ resultT.addTaskFragmentOperation(
+ fragmentToken,
+ TaskFragmentOperation.Builder(OP_TYPE_REORDER_TO_TOP_OF_TASK)
+ .build()
+ )
+
+ onCreateCallback(taskFragmentInfo)
+ }
+ TYPE_TASK_FRAGMENT_INFO_CHANGED -> {
+ onInfoChangedCallback(taskFragmentInfo)
+ }
+ TYPE_TASK_FRAGMENT_VANISHED -> {
+ hide()
+ }
+ TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED -> {}
+ TYPE_TASK_FRAGMENT_ERROR -> {
+ hide()
+ }
+ TYPE_ACTIVITY_REPARENTED_TO_TASK -> {}
+ else ->
+ throw IllegalArgumentException(
+ "Unknown TaskFragmentEvent=" + change.type
+ )
+ }
+ }
+ }
+ }
+ organizer.onTransactionHandled(
+ transaction.transactionToken,
+ resultT,
+ TASK_FRAGMENT_TRANSIT_CHANGE,
+ false
+ )
+ }
+
+ /** Creates the task fragment */
+ fun createTaskFragment() {
+ val taskBounds = Rect(activity.resources.configuration.windowConfiguration.bounds)
+ val fragmentOptions =
+ TaskFragmentCreationParams.Builder(
+ organizer.organizerToken,
+ fragmentToken,
+ activity.activityToken!!
+ )
+ .setInitialRelativeBounds(taskBounds)
+ .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+ .build()
+ organizer.applyTransaction(
+ WindowContainerTransaction().createTaskFragment(fragmentOptions),
+ TASK_FRAGMENT_TRANSIT_CHANGE,
+ false
+ )
+ }
+
+ private fun WindowContainerTransaction.startActivity(intent: Intent) =
+ this.startActivityInTaskFragment(fragmentToken, activity.activityToken!!, intent, null)
+
+ /** Starts the provided activity in the fragment and move it to the background */
+ fun startActivityInTaskFragment(intent: Intent) {
+ organizer.applyTransaction(
+ WindowContainerTransaction().startActivity(intent),
+ TASK_FRAGMENT_TRANSIT_OPEN,
+ false
+ )
+ }
+
+ /** Destroys the task fragment */
+ fun destroy() {
+ organizer.applyTransaction(
+ WindowContainerTransaction()
+ .addTaskFragmentOperation(
+ fragmentToken,
+ TaskFragmentOperation.Builder(OP_TYPE_DELETE_TASK_FRAGMENT).build()
+ ),
+ TASK_FRAGMENT_TRANSIT_CLOSE,
+ false
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
new file mode 100644
index 000000000000..91e0547ff93d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.domain.interactor
+
+import android.content.ComponentName
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
+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.kotlin.getOrNull
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
+class HomeControlsComponentInteractor
+@Inject
+constructor(
+ private val selectedComponentRepository: SelectedComponentRepository,
+ private val controlsComponent: ControlsComponent,
+ private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+ userRepository: UserRepository,
+ @Background private val bgScope: CoroutineScope
+) {
+ private val controlsListingController =
+ controlsComponent.getControlsListingController().getOrNull()
+
+ /** Gets the current user's selected panel, or null if there isn't one */
+ private val selectedItem: Flow<SelectedComponentRepository.SelectedComponent?> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { user ->
+ selectedComponentRepository.selectedComponentFlow(user.userHandle)
+ }
+ .map { if (it?.isPanel == true) it else null }
+
+ /** Gets all the available panels which are authorized by the user */
+ private fun allPanelItem(): Flow<List<PanelComponent>> {
+ if (controlsListingController == null) {
+ return emptyFlow()
+ }
+ return conflatedCallbackFlow {
+ val listener =
+ object : ControlsListingController.ControlsListingCallback {
+ override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+ trySend(serviceInfos)
+ }
+ }
+ controlsListingController.addCallback(listener)
+ awaitClose { controlsListingController.removeCallback(listener) }
+ }
+ .onStart { emit(controlsListingController.getCurrentServices()) }
+ .map { serviceInfos ->
+ val authorizedPanels = authorizedPanelsRepository.getAuthorizedPanels()
+ serviceInfos.mapNotNull {
+ val panelActivity = it.panelActivity
+ if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
+ PanelComponent(it.componentName, panelActivity)
+ } else {
+ null
+ }
+ }
+ }
+ }
+ val panelComponent: StateFlow<ComponentName?> =
+ combine(allPanelItem(), selectedItem) { items, selected ->
+ val item =
+ items.firstOrNull { it.componentName == selected?.componentName }
+ ?: items.firstOrNull()
+ item?.panelActivity
+ }
+ .stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
+
+ data class PanelComponent(val componentName: ComponentName, val panelActivity: ComponentName)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index c9b56a2ebd9a..05279fcdf51c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -23,7 +23,6 @@ import android.graphics.Region;
import android.view.GestureDetector;
import android.view.MotionEvent;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import java.util.Optional;
@@ -34,17 +33,14 @@ import javax.inject.Named;
/** {@link DreamTouchHandler} responsible for handling touches to open communal hub. **/
public class CommunalTouchHandler implements DreamTouchHandler {
private final int mInitiationWidth;
- private final NotificationShadeWindowController mNotificationShadeWindowController;
private final Optional<CentralSurfaces> mCentralSurfaces;
@Inject
public CommunalTouchHandler(
Optional<CentralSurfaces> centralSurfaces,
- NotificationShadeWindowController notificationShadeWindowController,
@Named(COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth) {
mInitiationWidth = initiationWidth;
mCentralSurfaces = centralSurfaces;
- mNotificationShadeWindowController = notificationShadeWindowController;
}
@Override
@@ -60,9 +56,8 @@ public class CommunalTouchHandler implements DreamTouchHandler {
}
private void handleSessionStart(CentralSurfaces surfaces, TouchSession session) {
- // Force the notification shade window open (otherwise the hub won't show while swiping).
- mNotificationShadeWindowController.setForcePluginOpen(true, this);
-
+ // Notification shade window has its own logic to be visible if the hub is open, no need to
+ // do anything here other than send touch events over.
session.registerInputListener(ev -> {
surfaces.handleDreamTouch((MotionEvent) ev);
if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 15404238099d..df0566e246a8 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -29,6 +29,8 @@ import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
+import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import javax.inject.Inject
@@ -45,6 +47,7 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
// Internal notification frontend dependencies
NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
+ NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
// Internal keyguard dependencies
KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c69c9ef93761..6eff79284847 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -585,10 +585,6 @@ object Flags {
@JvmField
val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button")
- // TODO(b/287205379): Tracking bug
- @JvmField
- val QS_CONTAINER_GRAPH_OPTIMIZER = releasedFlag( "qs_container_graph_optimizer")
-
/** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
@JvmField
val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog")
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/InputMethodModule.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/InputMethodModule.kt
new file mode 100644
index 000000000000..bac48f176712
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/InputMethodModule.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.inputmethod
+
+import com.android.systemui.inputmethod.data.repository.InputMethodRepositoryModule
+import dagger.Module
+
+/** Module for providing objects exposed by the input method package. */
+@Module(
+ includes =
+ [
+ InputMethodRepositoryModule::class,
+ ],
+)
+object InputMethodModule
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
new file mode 100644
index 000000000000..bdc18b322ac0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputmethod.data.model
+
+/**
+ * Models an input method editor (IME).
+ *
+ * @see android.view.inputmethod.InputMethodInfo
+ */
+data class InputMethodModel(
+ /** A unique ID for this input method. */
+ val imeId: String,
+ /** The subtypes of this IME (may be empty). */
+ val subtypes: List<Subtype>,
+) {
+ /**
+ * A Subtype can describe locale (e.g. en_US, fr_FR...) and mode (e.g. voice, keyboard), and is
+ * used for IME switch and settings.
+ *
+ * @see android.view.inputmethod.InputMethodSubtype
+ */
+ data class Subtype(
+ /** A unique ID for this IME subtype. */
+ val subtypeId: Int,
+ /**
+ * Whether this subtype is auxiliary. An auxiliary subtype will not be shown in the list of
+ * enabled IMEs for choosing the current IME in Settings, but it will be shown in the list
+ * of IMEs in the IME switcher to allow the user to tentatively switch to this subtype while
+ * an IME is shown.
+ *
+ * The intent of this flag is to allow for IMEs that are invoked in a one-shot way as
+ * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice
+ * input).
+ */
+ val isAuxiliary: Boolean,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
new file mode 100644
index 000000000000..5f316c4495ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.inputmethod.data.repository
+
+import android.annotation.SuppressLint
+import android.os.UserHandle
+import android.view.inputmethod.InputMethodInfo
+import android.view.inputmethod.InputMethodManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.inputmethod.data.model.InputMethodModel
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/** Provides access to input-method related application state in the bouncer. */
+interface InputMethodRepository {
+ /**
+ * Creates and returns a new `Flow` of installed input methods that are enabled for the
+ * specified user.
+ *
+ * @param fetchSubtypes Whether to fetch the IME Subtypes as well (requires an additional IPC
+ * call for each IME, avoid if not needed).
+ * @see InputMethodManager.getEnabledInputMethodListAsUser
+ */
+ suspend fun enabledInputMethods(userId: Int, fetchSubtypes: Boolean): Flow<InputMethodModel>
+
+ /** Returns enabled subtypes for the currently selected input method. */
+ suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype>
+
+ /**
+ * Shows the system's input method picker dialog.
+ *
+ * @param displayId The display ID on which to show the dialog.
+ * @param showAuxiliarySubtypes Whether to show auxiliary input method subtypes in the list of
+ * enabled IMEs.
+ * @see InputMethodManager.showInputMethodPickerFromSystem
+ */
+ suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean)
+}
+
+@SysUISingleton
+class InputMethodRepositoryImpl
+@Inject
+constructor(
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val inputMethodManager: InputMethodManager,
+) : InputMethodRepository {
+
+ override suspend fun enabledInputMethods(
+ userId: Int,
+ fetchSubtypes: Boolean
+ ): Flow<InputMethodModel> {
+ return withContext(backgroundDispatcher) {
+ inputMethodManager.getEnabledInputMethodListAsUser(UserHandle.of(userId))
+ }
+ .asFlow()
+ .map { inputMethodInfo ->
+ InputMethodModel(
+ imeId = inputMethodInfo.id,
+ subtypes =
+ if (fetchSubtypes) {
+ enabledInputMethodSubtypes(
+ inputMethodInfo,
+ allowsImplicitlyEnabledSubtypes = true
+ )
+ } else {
+ listOf()
+ }
+ )
+ }
+ }
+
+ override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> {
+ return enabledInputMethodSubtypes(
+ inputMethodInfo = null, // Fetch subtypes for the currently-selected IME.
+ allowsImplicitlyEnabledSubtypes = false
+ )
+ }
+
+ @SuppressLint("MissingPermission")
+ override suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean) {
+ withContext(backgroundDispatcher) {
+ inputMethodManager.showInputMethodPickerFromSystem(showAuxiliarySubtypes, displayId)
+ }
+ }
+
+ /**
+ * Returns a list of enabled input method subtypes for the specified input method info.
+ *
+ * @param inputMethodInfo The [InputMethodInfo] whose subtypes list will be returned. If `null`,
+ * returns enabled subtypes for the currently selected [InputMethodInfo].
+ * @param allowsImplicitlyEnabledSubtypes Whether to allow to return the implicitly enabled
+ * subtypes. If an input method info doesn't have enabled subtypes, the framework will
+ * implicitly enable subtypes according to the current system language.
+ * @see InputMethodManager.getEnabledInputMethodSubtypeList
+ */
+ private suspend fun enabledInputMethodSubtypes(
+ inputMethodInfo: InputMethodInfo?,
+ allowsImplicitlyEnabledSubtypes: Boolean
+ ): List<InputMethodModel.Subtype> {
+ return withContext(backgroundDispatcher) {
+ inputMethodManager.getEnabledInputMethodSubtypeList(
+ inputMethodInfo,
+ allowsImplicitlyEnabledSubtypes
+ )
+ }
+ .map {
+ InputMethodModel.Subtype(
+ subtypeId = it.subtypeId,
+ isAuxiliary = it.isAuxiliary,
+ )
+ }
+ }
+}
+
+@Module
+interface InputMethodRepositoryModule {
+ @Binds fun repository(impl: InputMethodRepositoryImpl): InputMethodRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
new file mode 100644
index 000000000000..c54aa7f2c6a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputmethod.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.inputmethod.data.repository.InputMethodRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.count
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.take
+
+/** Hosts application business logic related to input methods (e.g. software keyboard). */
+@SysUISingleton
+class InputMethodInteractor
+@Inject
+constructor(
+ private val repository: InputMethodRepository,
+) {
+ /**
+ * Returns whether there are multiple enabled input methods to choose from for password input.
+ *
+ * Method adapted from `com.android.inputmethod.latin.Utils`.
+ */
+ suspend fun hasMultipleEnabledImesOrSubtypes(userId: Int): Boolean {
+ // Count IMEs that either have no subtypes, or have at least one non-auxiliary subtype.
+ val matchingInputMethods =
+ repository
+ .enabledInputMethods(userId, fetchSubtypes = true)
+ .filter { ime -> ime.subtypes.isEmpty() || ime.subtypes.any { !it.isAuxiliary } }
+ .take(2) // Short-circuit if we find at least 2 matching IMEs.
+
+ return matchingInputMethods.count() > 1 || repository.selectedInputMethodSubtypes().size > 1
+ }
+
+ /** Shows the system's input method picker dialog. */
+ suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean) {
+ repository.showInputMethodPicker(displayId, showAuxiliarySubtypes)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
index 26e83a3f2179..f16a3bdfb975 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyboard
-import com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag
+import android.hardware.input.InputSettings
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
@@ -40,7 +40,7 @@ constructor(
if (featureFlags.isEnabled(Flags.KEYBOARD_BACKLIGHT_INDICATOR)) {
keyboardBacklightDialogCoordinator.get().startListening()
}
- if (keyboardA11yStickyKeysFlag()) {
+ if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
stickyKeysIndicatorCoordinator.get().startListening()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
index b68551bf93b3..c3a618d29c76 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
@@ -58,7 +58,6 @@ constructor(
dialog = null
} else if (dialog == null) {
dialog = ComposeFacade.createStickyKeysDialog(dialogFactory, viewModel).apply {
- setCanceledOnTouchOutside(false)
window?.setAttributes()
show()
}
@@ -70,6 +69,7 @@ constructor(
private fun Window.setAttributes() {
setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
+ addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
setGravity(Gravity.TOP or Gravity.END)
attributes = WindowManager.LayoutParams().apply {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index f10b87ee1cc5..b5f9c69a71b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -85,10 +85,10 @@ import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibil
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.shared.model.ScreenPowerState;
import com.android.systemui.settings.DisplayTracker;
+import com.android.wm.shell.shared.CounterRotator;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.CounterRotator;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Map;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index afef8751b065..7f43fac575cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -41,6 +41,7 @@ import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener
import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
@@ -86,6 +87,7 @@ constructor(
private val vibratorHelper: VibratorHelper,
private val falsingManager: FalsingManager,
private val aodAlphaViewModel: AodAlphaViewModel,
+ private val keyguardClockViewModel: KeyguardClockViewModel,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -113,7 +115,11 @@ constructor(
initializeViews()
if (!SceneContainerFlag.isEnabled) {
- KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel)
+ KeyguardBlueprintViewBinder.bind(
+ keyguardRootView,
+ keyguardBlueprintViewModel,
+ keyguardClockViewModel
+ )
}
keyguardBlueprintCommandListener.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index 48b663493e59..938183025336 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -23,6 +23,8 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType.DefaultTransition
import java.io.PrintWriter
import java.util.TreeMap
import javax.inject.Inject
@@ -54,6 +56,8 @@ constructor(
TreeMap<String, KeyguardBlueprint>().apply { putAll(blueprints.associateBy { it.id }) }
val blueprint: MutableStateFlow<KeyguardBlueprint> = MutableStateFlow(blueprintIdMap[DEFAULT]!!)
val refreshBluePrint: MutableSharedFlow<Unit> = MutableSharedFlow(extraBufferCapacity = 1)
+ val refreshBlueprintTransition: MutableSharedFlow<IntraBlueprintTransitionType> =
+ MutableSharedFlow(extraBufferCapacity = 1)
val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
/**
@@ -103,7 +107,12 @@ constructor(
/** Re-emits the last emitted blueprint value if possible. */
fun refreshBlueprint() {
+ refreshBlueprintWithTransition(DefaultTransition)
+ }
+
+ fun refreshBlueprintWithTransition(type: IntraBlueprintTransitionType = DefaultTransition) {
refreshBluePrint.tryEmit(Unit)
+ refreshBlueprintTransition.tryEmit(type)
}
/** Prints all available blueprints to the PrintWriter. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 9d38be941077..71d941ad8d22 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -28,6 +28,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleMultiple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -64,6 +65,7 @@ constructor(
listenForHubToAlternateBouncer()
listenForHubToOccluded()
listenForHubToGone()
+ listenForHubToDreaming()
}
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
@@ -131,6 +133,23 @@ constructor(
}
}
+ private fun listenForHubToDreaming() {
+ val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
+ scope.launch("$TAG#listenForHubToDreaming") {
+ keyguardInteractor.isAbleToDream
+ .sampleMultiple(startedKeyguardTransitionStep, finishedKeyguardState)
+ .collect { (isAbleToDream, lastStartedTransition, finishedKeyguardState) ->
+ val isOnHub = finishedKeyguardState == KeyguardState.GLANCEABLE_HUB
+ val isTransitionInterruptible =
+ lastStartedTransition.to == KeyguardState.GLANCEABLE_HUB &&
+ !invalidFromStates.contains(lastStartedTransition.from)
+ if (isAbleToDream && (isOnHub || isTransitionInterruptible)) {
+ startTransitionTo(KeyguardState.DREAMING)
+ }
+ }
+ }
+ }
+
private fun listenForHubToOccluded() {
scope.launch {
keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 3965648bd224..deb70b7f1086 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -405,7 +405,9 @@ constructor(
interpolator = Interpolators.LINEAR
duration =
when (toState) {
- KeyguardState.DREAMING -> TO_DREAMING_DURATION
+ // Adds 100ms to the overall delay to workaround legacy setOccluded calls
+ // being delayed in KeyguardViewMediator
+ KeyguardState.DREAMING -> TO_DREAMING_DURATION + 100.milliseconds
KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
KeyguardState.AOD -> TO_AOD_DURATION
KeyguardState.DOZING -> TO_DOZING_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index ba44e6832161..566e006598a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -24,12 +24,13 @@ import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType.DefaultTransition
import com.android.systemui.statusbar.policy.SplitShadeStateController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@@ -48,13 +49,15 @@ constructor(
*
* This flow can also emit the same blueprint value if refreshBlueprint is emitted.
*/
- val blueprint: Flow<KeyguardBlueprint> =
- merge(
- keyguardBlueprintRepository.blueprint,
- keyguardBlueprintRepository.refreshBluePrint.map {
- keyguardBlueprintRepository.blueprint.value
- }
- )
+ val blueprint: Flow<KeyguardBlueprint> = keyguardBlueprintRepository.blueprint
+
+ val blueprintWithTransition =
+ combine(
+ keyguardBlueprintRepository.refreshBluePrint,
+ keyguardBlueprintRepository.refreshBlueprintTransition
+ ) { _, source ->
+ source
+ }
init {
applicationScope.launch {
@@ -107,6 +110,10 @@ constructor(
keyguardBlueprintRepository.refreshBlueprint()
}
+ fun refreshBlueprintWithTransition(type: IntraBlueprintTransitionType = DefaultTransition) {
+ keyguardBlueprintRepository.refreshBlueprintWithTransition(type)
+ }
+
fun getCurrentBlueprint(): KeyguardBlueprint {
return keyguardBlueprintRepository.blueprint.value
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt
new file mode 100644
index 000000000000..a0f9be629132
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.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.keyguard.shared.model
+
+/** FROM -> TO keyguard transition. null values are allowed to signify FROM -> *, or * -> TO */
+data class Edge(
+ val from: KeyguardState?,
+ val to: KeyguardState?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 4abda741d495..00b798901352 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -22,6 +22,7 @@ import com.android.keyguard.logging.KeyguardTransitionAnimationLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
@@ -174,11 +175,6 @@ constructor(
}
}
- data class Edge(
- val from: KeyguardState?,
- val to: KeyguardState?,
- )
-
data class StateToValue(
val transitionState: TransitionState,
val value: Float?,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 8d6493f9a278..404046b00b66 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -26,7 +26,10 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.launch
@@ -34,7 +37,11 @@ class KeyguardBlueprintViewBinder {
companion object {
private const val TAG = "KeyguardBlueprintViewBinder"
- fun bind(constraintLayout: ConstraintLayout, viewModel: KeyguardBlueprintViewModel) {
+ fun bind(
+ constraintLayout: ConstraintLayout,
+ viewModel: KeyguardBlueprintViewModel,
+ clockViewModel: KeyguardClockViewModel
+ ) {
constraintLayout.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
@@ -42,6 +49,7 @@ class KeyguardBlueprintViewBinder {
val prevBluePrint = viewModel.currentBluePrint
Trace.beginSection("KeyguardBlueprint#applyBlueprint")
Log.d(TAG, "applying blueprint: $blueprint")
+ TransitionManager.endTransitions(constraintLayout)
val cs =
ConstraintSet().apply {
@@ -54,11 +62,28 @@ class KeyguardBlueprintViewBinder {
}
// Apply transition.
- if (!keyguardBottomAreaRefactor() && prevBluePrint != null &&
- prevBluePrint != blueprint) {
+ if (
+ !keyguardBottomAreaRefactor() &&
+ prevBluePrint != null &&
+ prevBluePrint != blueprint
+ ) {
TransitionManager.beginDelayedTransition(
constraintLayout,
- BaseBlueprintTransition()
+ BaseBlueprintTransition(clockViewModel)
+ .addTransition(
+ IntraBlueprintTransition(
+ IntraBlueprintTransitionType.NoTransition,
+ clockViewModel
+ )
+ )
+ )
+ } else {
+ TransitionManager.beginDelayedTransition(
+ constraintLayout,
+ IntraBlueprintTransition(
+ IntraBlueprintTransitionType.NoTransition,
+ clockViewModel
+ )
)
}
@@ -71,6 +96,25 @@ class KeyguardBlueprintViewBinder {
Trace.endSection()
}
}
+
+ launch {
+ viewModel.blueprintWithTransition.collect { source ->
+ TransitionManager.endTransitions(constraintLayout)
+
+ val cs =
+ ConstraintSet().apply {
+ clone(constraintLayout)
+ viewModel.currentBluePrint?.applyConstraints(this)
+ }
+
+ TransitionManager.beginDelayedTransition(
+ constraintLayout,
+ IntraBlueprintTransition(source, clockViewModel)
+ )
+ cs.applyTo(constraintLayout)
+ Trace.endSection()
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 3c3ebdfc066b..f0e89f9695de 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -16,34 +16,30 @@
package com.android.systemui.keyguard.ui.binder
-import android.animation.Animator
-import android.animation.ValueAnimator
-import android.transition.Transition
import android.transition.TransitionManager
import android.transition.TransitionSet
-import android.transition.TransitionValues
-import android.view.ViewGroup
+import android.util.Log
+import android.view.View.INVISIBLE
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.helper.widget.Layer
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
+import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
import kotlinx.coroutines.launch
-private const val KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS = 1000L
-
object KeyguardClockViewBinder {
@JvmStatic
fun bind(
@@ -67,32 +63,56 @@ object KeyguardClockViewBinder {
viewModel.clock = currentClock
addClockViews(currentClock, keyguardRootView)
updateBurnInLayer(keyguardRootView, viewModel)
- blueprintInteractor.refreshBlueprint()
+ applyConstraints(clockSection, keyguardRootView, true)
}
}
launch {
if (!migrateClocksToBlueprint()) return@launch
viewModel.clockSize.collect {
updateBurnInLayer(keyguardRootView, viewModel)
- blueprintInteractor.refreshBlueprint()
+ blueprintInteractor.refreshBlueprintWithTransition(
+ IntraBlueprintTransitionType.ClockSize
+ )
}
}
launch {
if (!migrateClocksToBlueprint()) return@launch
- viewModel.clockShouldBeCentered.collect {
+ viewModel.clockShouldBeCentered.collect { clockShouldBeCentered ->
+ Log.d(
+ "ClockViewBinder",
+ "Sherry clockShouldBeCentered $clockShouldBeCentered"
+ )
viewModel.clock?.let {
- if (it.largeClock.config.hasCustomPositionUpdatedAnimation) {
- playClockCenteringAnimation(clockSection, keyguardRootView, it)
+ // Weather clock also has hasCustomPositionUpdatedAnimation as true
+ // TODO(b/323020908): remove ID check
+ if (
+ it.largeClock.config.hasCustomPositionUpdatedAnimation &&
+ it.config.id == DEFAULT_CLOCK_ID
+ ) {
+ blueprintInteractor.refreshBlueprintWithTransition(
+ IntraBlueprintTransitionType.DefaultClockStepping
+ )
} else {
- blueprintInteractor.refreshBlueprint()
+ blueprintInteractor.refreshBlueprintWithTransition(
+ IntraBlueprintTransitionType.DefaultTransition
+ )
}
}
}
}
launch {
if (!migrateClocksToBlueprint()) return@launch
- viewModel.isAodIconsVisible.collect {
- applyConstraints(clockSection, keyguardRootView, true)
+ viewModel.isAodIconsVisible.collect { isAodIconsVisible ->
+ viewModel.clock?.let {
+ // Weather clock also has hasCustomPositionUpdatedAnimation as true
+ if (
+ viewModel.useLargeClock && it.config.id == "DIGITAL_CLOCK_WEATHER"
+ ) {
+ blueprintInteractor.refreshBlueprintWithTransition(
+ IntraBlueprintTransitionType.DefaultTransition
+ )
+ }
+ }
}
}
}
@@ -155,7 +175,9 @@ object KeyguardClockViewBinder {
}
// small clock should either be a single view or container with id
// `lockscreen_clock_view`
- clock.smallClock.layout.views.forEach { rootView.addView(it) }
+ clock.smallClock.layout.views.forEach {
+ rootView.addView(it).apply { it.visibility = INVISIBLE }
+ }
clock.largeClock.layout.views.forEach { rootView.addView(it) }
}
}
@@ -173,74 +195,4 @@ object KeyguardClockViewBinder {
}
constraintSet.applyTo(rootView)
}
-
- private fun playClockCenteringAnimation(
- clockSection: ClockSection,
- keyguardRootView: ConstraintLayout,
- clock: ClockController,
- ) {
- // Find the clock, so we can exclude it from this transition.
- val clockView = clock.largeClock.view
- val set = TransitionSet()
- val adapter = SplitShadeTransitionAdapter(clock)
- adapter.setInterpolator(Interpolators.LINEAR)
- adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS)
- adapter.addTarget(clockView)
- set.addTransition(adapter)
- applyConstraints(clockSection, keyguardRootView, true, set)
- }
-
- internal class SplitShadeTransitionAdapter
- @VisibleForTesting
- constructor(private val clock: ClockController) : Transition() {
- private fun captureValues(transitionValues: TransitionValues) {
- transitionValues.values[PROP_BOUNDS_LEFT] = transitionValues.view.left
- val locationInWindowTmp = IntArray(2)
- transitionValues.view.getLocationInWindow(locationInWindowTmp)
- transitionValues.values[PROP_X_IN_WINDOW] = locationInWindowTmp[0]
- }
-
- override fun captureEndValues(transitionValues: TransitionValues) {
- captureValues(transitionValues)
- }
-
- override fun captureStartValues(transitionValues: TransitionValues) {
- captureValues(transitionValues)
- }
-
- override fun createAnimator(
- sceneRoot: ViewGroup,
- startValues: TransitionValues?,
- endValues: TransitionValues?
- ): Animator? {
- if (startValues == null || endValues == null) {
- return null
- }
- val anim = ValueAnimator.ofFloat(0f, 1f)
- val fromLeft = startValues.values[PROP_BOUNDS_LEFT] as Int
- val fromWindowX = startValues.values[PROP_X_IN_WINDOW] as Int
- val toWindowX = endValues.values[PROP_X_IN_WINDOW] as Int
- // Using windowX, to determine direction, instead of left, as in RTL the difference of
- // toLeft - fromLeft is always positive, even when moving left.
- val direction = if (toWindowX - fromWindowX > 0) 1 else -1
- anim.addUpdateListener { animation: ValueAnimator ->
- clock.largeClock.animations.onPositionUpdated(
- fromLeft,
- direction,
- animation.animatedFraction
- )
- }
- return anim
- }
-
- override fun getTransitionProperties(): Array<String> {
- return TRANSITION_PROPERTIES
- }
-
- companion object {
- private const val PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft"
- private const val PROP_X_IN_WINDOW = "splitShadeTransitionAdapter:xInWindow"
- private val TRANSITION_PROPERTIES = arrayOf(PROP_BOUNDS_LEFT, PROP_X_IN_WINDOW)
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 10392e3c1450..08a2b9cb8968 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -49,14 +49,15 @@ object KeyguardSmartspaceViewBinder {
clockViewModel,
smartspaceViewModel
)
- blueprintInteractor.refreshBlueprint()
+ blueprintInteractor.refreshBlueprintWithTransition()
}
}
launch {
+ if (!migrateClocksToBlueprint()) return@launch
smartspaceViewModel.bcSmartspaceVisibility.collect {
updateBCSmartspaceInBurnInLayer(keyguardRootView, clockViewModel)
- blueprintInteractor.refreshBlueprint()
+ blueprintInteractor.refreshBlueprintWithTransition()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
index fd530b77707a..9c9df806c38c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
@@ -19,30 +19,32 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints.transitions
import android.animation.Animator
import android.animation.ObjectAnimator
import android.transition.ChangeBounds
+import android.transition.Transition
import android.transition.TransitionSet
import android.transition.TransitionValues
import android.transition.Visibility
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.helper.widget.Layer
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
-import com.android.systemui.res.R
-class BaseBlueprintTransition : TransitionSet() {
+class BaseBlueprintTransition(val clockViewModel: KeyguardClockViewModel) : TransitionSet() {
init {
ordering = ORDERING_SEQUENTIAL
addTransition(AlphaOutVisibility())
.addTransition(ChangeBounds())
.addTransition(AlphaInVisibility())
excludeTarget(Layer::class.java, /* exclude= */ true)
- excludeClockAndSmartspaceViews()
+ excludeClockAndSmartspaceViews(this)
}
- private fun excludeClockAndSmartspaceViews() {
- excludeTarget(R.id.lockscreen_clock_view, true)
- excludeTarget(R.id.lockscreen_clock_view_large, true)
- excludeTarget(SmartspaceView::class.java, true)
- // TODO(b/319468190): need to exclude views from large weather clock
+ private fun excludeClockAndSmartspaceViews(transition: Transition) {
+ transition.excludeTarget(SmartspaceView::class.java, true)
+ clockViewModel.clock?.let { clock ->
+ clock.largeClock.layout.views.forEach { view -> transition.excludeTarget(view, true) }
+ clock.smallClock.layout.views.forEach { view -> transition.excludeTarget(view, true) }
+ }
}
class AlphaOutVisibility : Visibility() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
new file mode 100644
index 000000000000..524aa1a95d2d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.blueprints.transitions
+
+import android.transition.TransitionSet
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.DefaultClockSteppingTransition
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+
+enum class IntraBlueprintTransitionType {
+ ClockSize,
+ ClockCenter,
+ DefaultClockStepping,
+ DefaultTransition,
+ AodNotifIconsTransition,
+ // When transition between blueprint, we don't need any duration or interpolator but we need
+ // all elements go to correct state
+ NoTransition,
+}
+
+class IntraBlueprintTransition(
+ type: IntraBlueprintTransitionType,
+ clockViewModel: KeyguardClockViewModel
+) : TransitionSet() {
+ init {
+ ordering = ORDERING_TOGETHER
+ if (type == IntraBlueprintTransitionType.DefaultClockStepping)
+ addTransition(clockViewModel.clock?.let { DefaultClockSteppingTransition(it) })
+ addTransition(ClockSizeTransition(type, clockViewModel))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index fe4f07d022dd..b1178f5aaea5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -166,10 +166,7 @@ constructor(
context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
largeClockTopMargin += getDimen(DATE_WEATHER_VIEW_HEIGHT)
largeClockTopMargin += getDimen(ENHANCED_SMARTSPACE_HEIGHT)
- if (!keyguardClockViewModel.useLargeClock) {
- largeClockTopMargin -=
- context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
- }
+
connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
constrainWidth(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
new file mode 100644
index 000000000000..99565b104a00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections.transitions
+
+import android.animation.Animator
+import android.animation.ObjectAnimator
+import android.transition.ChangeBounds
+import android.transition.TransitionSet
+import android.transition.TransitionValues
+import android.transition.Visibility
+import android.view.View
+import android.view.ViewGroup
+import com.android.app.animation.Interpolators
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.res.R
+import com.android.systemui.shared.R as sharedR
+
+const val CLOCK_OUT_MILLIS = 133L
+const val CLOCK_IN_MILLIS = 167L
+val CLOCK_IN_INTERPOLATOR = Interpolators.LINEAR_OUT_SLOW_IN
+const val CLOCK_IN_START_DELAY_MILLIS = 133L
+val CLOCK_OUT_INTERPOLATOR = Interpolators.LINEAR
+
+class ClockSizeTransition(
+ val type: IntraBlueprintTransitionType,
+ clockViewModel: KeyguardClockViewModel
+) : TransitionSet() {
+ init {
+ ordering = ORDERING_TOGETHER
+ addTransition(ClockOutTransition(clockViewModel, type))
+ addTransition(ClockInTransition(clockViewModel, type))
+ addTransition(SmartspaceChangeBounds(clockViewModel, type))
+ addTransition(ClockInChangeBounds(clockViewModel, type))
+ addTransition(ClockOutChangeBounds(clockViewModel, type))
+ }
+
+ class ClockInTransition(viewModel: KeyguardClockViewModel, type: IntraBlueprintTransitionType) :
+ Visibility() {
+ init {
+ mode = MODE_IN
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration = CLOCK_IN_MILLIS
+ startDelay = CLOCK_IN_START_DELAY_MILLIS
+ interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ } else {
+ duration = 0
+ startDelay = 0
+ }
+
+ addTarget(sharedR.id.bc_smartspace_view)
+ addTarget(sharedR.id.date_smartspace_view)
+ addTarget(sharedR.id.weather_smartspace_view)
+ if (viewModel.useLargeClock) {
+ viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
+ } else {
+ addTarget(R.id.lockscreen_clock_view)
+ }
+ }
+
+ override fun onAppear(
+ sceneRoot: ViewGroup?,
+ view: View,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ): Animator {
+ return ObjectAnimator.ofFloat(view, "alpha", 1f).also {
+ it.duration = duration
+ it.startDelay = startDelay
+ it.interpolator = interpolator
+ it.addUpdateListener { view.alpha = it.animatedValue as Float }
+ it.start()
+ }
+ }
+ }
+
+ class ClockOutTransition(
+ viewModel: KeyguardClockViewModel,
+ type: IntraBlueprintTransitionType
+ ) : Visibility() {
+ init {
+ mode = MODE_OUT
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration = CLOCK_OUT_MILLIS
+ interpolator = CLOCK_OUT_INTERPOLATOR
+ } else {
+ duration = 0
+ }
+
+ addTarget(sharedR.id.bc_smartspace_view)
+ addTarget(sharedR.id.date_smartspace_view)
+ addTarget(sharedR.id.weather_smartspace_view)
+ if (viewModel.useLargeClock) {
+ addTarget(R.id.lockscreen_clock_view)
+ } else {
+ viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
+ }
+ }
+
+ override fun onDisappear(
+ sceneRoot: ViewGroup?,
+ view: View,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ): Animator {
+ return ObjectAnimator.ofFloat(view, "alpha", 0f).also {
+ it.duration = duration
+ it.interpolator = interpolator
+ it.addUpdateListener { view.alpha = it.animatedValue as Float }
+ it.start()
+ }
+ }
+ }
+
+ class ClockInChangeBounds(
+ viewModel: KeyguardClockViewModel,
+ type: IntraBlueprintTransitionType
+ ) : ChangeBounds() {
+ init {
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration = CLOCK_IN_MILLIS
+ startDelay = CLOCK_IN_START_DELAY_MILLIS
+ interpolator = CLOCK_IN_INTERPOLATOR
+ } else {
+ duration = 0
+ startDelay = 0
+ }
+
+ if (viewModel.useLargeClock) {
+ viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
+ } else {
+ addTarget(R.id.lockscreen_clock_view)
+ }
+ }
+ }
+
+ class ClockOutChangeBounds(
+ viewModel: KeyguardClockViewModel,
+ type: IntraBlueprintTransitionType
+ ) : ChangeBounds() {
+ init {
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration = CLOCK_OUT_MILLIS
+ interpolator = CLOCK_OUT_INTERPOLATOR
+ } else {
+ duration = 0
+ }
+ if (viewModel.useLargeClock) {
+ addTarget(R.id.lockscreen_clock_view)
+ } else {
+ viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
+ }
+ }
+ }
+
+ class SmartspaceChangeBounds(
+ viewModel: KeyguardClockViewModel,
+ val type: IntraBlueprintTransitionType = IntraBlueprintTransitionType.DefaultTransition
+ ) : ChangeBounds() {
+ init {
+ if (type != IntraBlueprintTransitionType.NoTransition) {
+ duration =
+ if (viewModel.useLargeClock) {
+ STATUS_AREA_MOVE_UP_MILLIS
+ } else {
+ STATUS_AREA_MOVE_DOWN_MILLIS
+ }
+ interpolator = Interpolators.EMPHASIZED
+ } else {
+ duration = 0
+ }
+ addTarget(sharedR.id.date_smartspace_view)
+ addTarget(sharedR.id.weather_smartspace_view)
+ addTarget(sharedR.id.bc_smartspace_view)
+ }
+
+ companion object {
+ const val STATUS_AREA_MOVE_UP_MILLIS = 967L
+ const val STATUS_AREA_MOVE_DOWN_MILLIS = 467L
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt
new file mode 100644
index 000000000000..c35dad71a584
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.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.keyguard.ui.view.layout.sections.transitions
+
+import android.animation.Animator
+import android.animation.ValueAnimator
+import android.transition.Transition
+import android.transition.TransitionValues
+import android.view.ViewGroup
+import com.android.app.animation.Interpolators
+import com.android.systemui.plugins.clocks.ClockController
+
+class DefaultClockSteppingTransition(private val clock: ClockController) : Transition() {
+ init {
+ interpolator = Interpolators.LINEAR
+ duration = KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS
+ addTarget(clock.largeClock.view)
+ }
+ private fun captureValues(transitionValues: TransitionValues) {
+ transitionValues.values[PROP_BOUNDS_LEFT] = transitionValues.view.left
+ val locationInWindowTmp = IntArray(2)
+ transitionValues.view.getLocationInWindow(locationInWindowTmp)
+ transitionValues.values[PROP_X_IN_WINDOW] = locationInWindowTmp[0]
+ }
+
+ override fun captureEndValues(transitionValues: TransitionValues) {
+ captureValues(transitionValues)
+ }
+
+ override fun captureStartValues(transitionValues: TransitionValues) {
+ captureValues(transitionValues)
+ }
+
+ override fun createAnimator(
+ sceneRoot: ViewGroup,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ): Animator? {
+ if (startValues == null || endValues == null) {
+ return null
+ }
+ val anim = ValueAnimator.ofFloat(0f, 1f)
+ val fromLeft = startValues.values[PROP_BOUNDS_LEFT] as Int
+ val fromWindowX = startValues.values[PROP_X_IN_WINDOW] as Int
+ val toWindowX = endValues.values[PROP_X_IN_WINDOW] as Int
+ // Using windowX, to determine direction, instead of left, as in RTL the difference of
+ // toLeft - fromLeft is always positive, even when moving left.
+ val direction = if (toWindowX - fromWindowX > 0) 1 else -1
+ anim.addUpdateListener { animation: ValueAnimator ->
+ clock.largeClock.animations.onPositionUpdated(
+ fromLeft,
+ direction,
+ animation.animatedFraction
+ )
+ }
+ return anim
+ }
+
+ override fun getTransitionProperties(): Array<String> {
+ return TRANSITION_PROPERTIES
+ }
+
+ companion object {
+ private const val PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft"
+ private const val PROP_X_IN_WINDOW = "splitShadeTransitionAdapter:xInWindow"
+ private val TRANSITION_PROPERTIES = arrayOf(PROP_BOUNDS_LEFT, PROP_X_IN_WINDOW)
+ private const val KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS = 1000L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 310ec95a22df..ad6a36c71e39 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -27,6 +27,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlin.math.roundToInt
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -74,7 +75,18 @@ constructor(
isTransitionToAod && isUdfps
}
.distinctUntilChanged()
- private val padding: Flow<Int> = udfpsOverlayInteractor.iconPadding
+
+ private val padding: Flow<Int> =
+ deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { udfpsSupported ->
+ if (udfpsSupported) {
+ udfpsOverlayInteractor.iconPadding
+ } else {
+ configurationInteractor.scaleForResolution.map { scale ->
+ (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+ .roundToInt()
+ }
+ }
+ }
val viewModel: Flow<ForegroundIconViewModel> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
index e2bfc36aee37..d22856b1bec2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
@@ -26,4 +26,5 @@ class KeyguardBlueprintViewModel
constructor(keyguardBlueprintInteractor: KeyguardBlueprintInteractor) {
var currentBluePrint: KeyguardBlueprint? = null
val blueprint = keyguardBlueprintInteractor.blueprint
+ val blueprintWithTransition = keyguardBlueprintInteractor.blueprintWithTransition
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt
new file mode 100644
index 000000000000..fe7dc4bdf59f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [TableLogBuffer] for communal-related logging. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CommunalTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 3e0094081638..23029e6e158b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -579,6 +579,16 @@ public class LogModule {
return factory.create("CommunalLog", 250);
}
+ /**
+ * Provides a {@link TableLogBuffer} for communal-related logs.
+ */
+ @Provides
+ @SysUISingleton
+ @CommunalTableLog
+ public static TableLogBuffer provideCommunalTableLogBuffer(TableLogBufferFactory factory) {
+ return factory.create("CommunalTableLog", 250);
+ }
+
/** Provides a {@link LogBuffer} for display metrics related logs. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 523414cfddbf..c33ab12406a0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -586,7 +586,7 @@ constructor(
coroutineScope.launch {
communalInteractor.isCommunalShowing.collect { value ->
isCommunalShowing = value
- updateDesiredLocation(forceNoAnimation = true)
+ updateDesiredLocation()
}
}
}
@@ -1149,13 +1149,17 @@ constructor(
when {
mediaFlags.isSceneContainerEnabled() -> desiredLocation
dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
+
+ // UMO should show in communal unless the shade is expanding or visible.
+ isCommunalShowing && qsExpansion == 0.0f -> LOCATION_COMMUNAL_HUB
(qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
- // TODO(b/311234666): revisit logic once interactions between the hub and
- // shade/keyguard state are finalized
- isCommunalShowing && communalInteractor.isCommunalEnabled -> LOCATION_COMMUNAL_HUB
+
+ // Communal does not have its own StatusBarState so it should always have higher
+ // priority for the UMO over the lockscreen.
+ isCommunalShowing -> LOCATION_COMMUNAL_HUB
onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
else -> LOCATION_QQS
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index ac0bd29e22d6..1c37510fde34 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -77,6 +77,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
private Consumer<Boolean> mMediaVisibilityChangedListener;
@Orientation
private int mLastOrientation;
+ private int mLastScreenLayout;
private String mCachedSpecs = "";
@Nullable
private QSTileRevealController mQsTileRevealController;
@@ -93,15 +94,19 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
public void onConfigurationChange(Configuration newConfig) {
final boolean previousSplitShadeState = mShouldUseSplitNotificationShade;
final int previousOrientation = mLastOrientation;
+ final int previousScreenLayout = mLastScreenLayout;
mShouldUseSplitNotificationShade = mSplitShadeStateController
.shouldUseSplitNotificationShade(getResources());
mLastOrientation = newConfig.orientation;
+ mLastScreenLayout = newConfig.screenLayout;
mQSLogger.logOnConfigurationChanged(
/* oldOrientation= */ previousOrientation,
/* newOrientation= */ mLastOrientation,
/* oldShouldUseSplitShade= */ previousSplitShadeState,
/* newShouldUseSplitShade= */ mShouldUseSplitNotificationShade,
+ /* oldScreenLayout= */ previousScreenLayout,
+ /* newScreenLayout= */ mLastScreenLayout,
/* containerName= */ mView.getDumpableTag());
switchTileLayoutIfNeeded();
@@ -198,6 +203,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
setTiles();
mLastOrientation = getResources().getConfiguration().orientation;
+ mLastScreenLayout = getResources().getConfiguration().screenLayout;
mQSLogger.logOnViewAttached(mLastOrientation, mView.getDumpableTag());
switchTileLayout(true);
@@ -443,11 +449,13 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
}
boolean shouldUseHorizontalLayout() {
- if (mShouldUseSplitNotificationShade) {
+ if (mShouldUseSplitNotificationShade) {
return false;
}
return mUsingMediaPlayer && mMediaHost.getVisible()
- && mLastOrientation == Configuration.ORIENTATION_LANDSCAPE;
+ && mLastOrientation == Configuration.ORIENTATION_LANDSCAPE
+ && (mLastScreenLayout & Configuration.SCREENLAYOUT_LONG_MASK)
+ == Configuration.SCREENLAYOUT_LONG_YES;
}
private void logTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 38e797228d4f..b515ce07cc02 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -19,6 +19,8 @@ package com.android.systemui.qs.logging
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.content.res.Configuration.Orientation
+import android.content.res.Configuration.SCREENLAYOUT_LONG_NO
+import android.content.res.Configuration.SCREENLAYOUT_LONG_YES
import android.service.quicksettings.Tile
import android.view.View
import com.android.systemui.log.ConstantStringsLogger
@@ -266,8 +268,10 @@ constructor(
fun logOnConfigurationChanged(
@Orientation oldOrientation: Int,
@Orientation newOrientation: Int,
- newShouldUseSplitShade: Boolean,
oldShouldUseSplitShade: Boolean,
+ newShouldUseSplitShade: Boolean,
+ oldScreenLayout: Int,
+ newScreenLayout: Int,
containerName: String
) {
configChangedBuffer.log(
@@ -277,6 +281,8 @@ constructor(
str1 = containerName
int1 = oldOrientation
int2 = newOrientation
+ long1 = oldScreenLayout.toLong()
+ long2 = newScreenLayout.toLong()
bool1 = oldShouldUseSplitShade
bool2 = newShouldUseSplitShade
},
@@ -284,6 +290,8 @@ constructor(
"config change: " +
"$str1 orientation=${toOrientationString(int2)} " +
"(was ${toOrientationString(int1)}), " +
+ "screen layout=${toScreenLayoutString(long1.toInt())} " +
+ "(was ${toScreenLayoutString(long2.toInt())}), " +
"splitShade=$bool2 (was $bool1)"
}
)
@@ -370,3 +378,11 @@ private inline fun toOrientationString(@Orientation orientation: Int): String {
else -> "undefined"
}
}
+
+private inline fun toScreenLayoutString(screenLayout: Int): String {
+ return when (screenLayout) {
+ SCREENLAYOUT_LONG_YES -> "long"
+ SCREENLAYOUT_LONG_NO -> "notlong"
+ else -> "undefined"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index c5eeb2f87d3a..9fefcbd86e51 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -52,7 +52,7 @@ import com.android.systemui.animation.LaunchableView
import com.android.systemui.animation.LaunchableViewDelegate
import com.android.systemui.plugins.qs.QSIconView
import com.android.systemui.plugins.qs.QSTile
-import com.android.systemui.plugins.qs.QSTile.BooleanState
+import com.android.systemui.plugins.qs.QSTile.AdapterState
import com.android.systemui.plugins.qs.QSTileView
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
@@ -506,13 +506,12 @@ open class QSTileViewImpl @JvmOverloads constructor(
state.expandedAccessibilityClassName
}
- if (state is BooleanState) {
+ if (state is AdapterState) {
val newState = state.value
if (tileState != newState) {
tileState = newState
}
}
- //
// Labels
if (!Objects.equals(label.text, state.label)) {
@@ -625,7 +624,7 @@ open class QSTileViewImpl @JvmOverloads constructor(
customDrawableView.setImageDrawable(state.sideViewCustomDrawable)
customDrawableView.visibility = VISIBLE
chevronView.visibility = GONE
- } else if (state !is BooleanState || state.forceExpandIcon) {
+ } else if (state !is AdapterState || state.forceExpandIcon) {
customDrawableView.setImageDrawable(null)
customDrawableView.visibility = GONE
chevronView.visibility = VISIBLE
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
new file mode 100644
index 000000000000..26069c774364
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
@@ -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.systemui.qs.tiles.impl.fontscaling.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.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [FontScalingTileModel] to [QSTileState]. */
+class FontScalingTileMapper
+@Inject
+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) {
+ val icon =
+ Icon.Loaded(
+ resources.getDrawable(
+ R.drawable.ic_qs_font_scaling,
+ theme,
+ ),
+ contentDescription = null
+ )
+ this.icon = { icon }
+ contentDescription = label
+ activationState = QSTileState.ActivationState.ACTIVE
+ sideViewIcon = QSTileState.SideViewIcon.Chevron
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt
new file mode 100644
index 000000000000..745e6a301689
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Provides [FontScalingTileModel]. */
+class FontScalingTileDataInteractor @Inject constructor() :
+ QSTileDataInteractor<FontScalingTileModel> {
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<FontScalingTileModel> = flowOf(FontScalingTileModel)
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
new file mode 100644
index 000000000000..b6f4afb84259
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles font scaling tile clicks. */
+class FontScalingTileUserActionInteractor
+@Inject
+constructor(
+ @Main private val coroutineContext: CoroutineContext,
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val fontScalingDialogDelegateProvider: Provider<FontScalingDialogDelegate>,
+ private val keyguardStateController: KeyguardStateController,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val activityStarter: ActivityStarter,
+) : QSTileUserActionInteractor<FontScalingTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<FontScalingTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ // We animate from the touched view only if we are not on the keyguard
+ val animateFromView: Boolean =
+ action.view != null && !keyguardStateController.isShowing
+ val runnable = Runnable {
+ val dialog: SystemUIDialog =
+ fontScalingDialogDelegateProvider.get().createDialog()
+ if (animateFromView) {
+ dialogLaunchAnimator.showFromView(
+ dialog,
+ action.view!!,
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ )
+ )
+ } else {
+ dialog.show()
+ }
+ }
+
+ withContext(coroutineContext) {
+ activityStarter.executeRunnableDismissingKeyguard(
+ runnable,
+ /* cancelAction= */ null,
+ /* dismissShade= */ true,
+ /* afterKeyguardGone= */ true,
+ /* deferred= */ false
+ )
+ }
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_TEXT_READING_SETTINGS)
+ )
+ }
+ }
+ }
+ companion object {
+ private const val INTERACTION_JANK_TAG = "font_scaling"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt
new file mode 100644
index 000000000000..76042dffa47e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain.model
+
+/** FontScaling tile model. No data needed as the tile just opens a dialog. */
+data object FontScalingTileModel
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 4780a2e9ccee..5a389f3fe701 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
@@ -227,7 +227,7 @@ constructor(
): QSTile.State =
// we have to use QSTile.BooleanState to support different side icons
// which are bound to instanceof QSTile.BooleanState in QSTileView.
- QSTile.BooleanState().apply {
+ QSTile.AdapterState().apply {
spec = config.tileSpec.spec
label = viewModelState.label
// This value is synthetic and doesn't have any meaning. It's only needed to satisfy
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 246ccb1180b0..37abc4066e54 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
@@ -42,6 +42,7 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
+import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printSection
import com.android.systemui.util.println
@@ -55,6 +56,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
@@ -81,6 +83,7 @@ constructor(
private val simBouncerInteractor: Lazy<SimBouncerInteractor>,
private val authenticationInteractor: Lazy<AuthenticationInteractor>,
private val windowController: NotificationShadeWindowController,
+ private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
) : CoreStartable {
override fun start() {
@@ -112,26 +115,33 @@ constructor(
private fun hydrateVisibility() {
applicationScope.launch {
// TODO(b/296114544): Combine with some global hun state to make it visible!
- sceneInteractor.transitionState
- .mapNotNull { state ->
- when (state) {
- is ObservableTransitionState.Idle -> {
- if (state.scene != SceneKey.Gone) {
- true to "scene is not Gone"
- } else {
- false to "scene is Gone"
- }
- }
- is ObservableTransitionState.Transition -> {
- if (state.fromScene == SceneKey.Gone) {
- true to "scene transitioning away from Gone"
- } else {
- null
+ deviceProvisioningInteractor.isFactoryResetProtectionActive
+ .flatMapLatest { isFrpActive ->
+ if (isFrpActive) {
+ flowOf(false to "Factory Reset Protection is active")
+ } else {
+ sceneInteractor.transitionState
+ .mapNotNull { state ->
+ when (state) {
+ is ObservableTransitionState.Idle -> {
+ if (state.scene != SceneKey.Gone) {
+ true to "scene is not Gone"
+ } else {
+ false to "scene is Gone"
+ }
+ }
+ is ObservableTransitionState.Transition -> {
+ if (state.fromScene == SceneKey.Gone) {
+ true to "scene transitioning away from Gone"
+ } else {
+ null
+ }
+ }
+ }
}
- }
+ .distinctUntilChanged()
}
}
- .distinctUntilChanged()
.collect { (isVisible, loggingReason) ->
sceneInteractor.setVisible(isVisible, loggingReason)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 7aa0dadba16c..b5a13138a278 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -25,7 +25,6 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.media.MediaRecorder;
import android.net.Uri;
@@ -379,10 +378,9 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
.addExtras(extras);
// Add thumbnail if available
- Bitmap thumbnailBitmap = recording.getThumbnail();
- if (thumbnailBitmap != null) {
+ if (recording.getThumbnail() != null) {
Notification.BigPictureStyle pictureStyle = new Notification.BigPictureStyle()
- .bigPicture(thumbnailBitmap)
+ .bigPicture(recording.getThumbnail())
.showBigPictureWhenCollapsed(true);
builder.setStyle(pictureStyle);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index a170d0dac100..5469a4e9da08 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -23,10 +23,12 @@ import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC;
import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaCodec;
@@ -51,6 +53,7 @@ import android.util.Size;
import android.view.Surface;
import android.view.WindowManager;
+import com.android.internal.R;
import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget;
import java.io.Closeable;
@@ -361,14 +364,27 @@ public class ScreenMediaRecorder extends MediaProjection.Callback {
Files.copy(mTempVideoFile.toPath(), os);
os.close();
if (mTempAudioFile != null) mTempAudioFile.delete();
- DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
- Size size = new Size(metrics.widthPixels, metrics.heightPixels);
- SavedRecording recording = new SavedRecording(itemUri, mTempVideoFile, size);
+ SavedRecording recording = new SavedRecording(
+ itemUri, mTempVideoFile, getRequiredThumbnailSize());
mTempVideoFile.delete();
return recording;
}
/**
+ * Returns the required {@code Size} of the thumbnail.
+ */
+ private Size getRequiredThumbnailSize() {
+ boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
+ int thumbnailIconHeight = mContext.getResources().getDimensionPixelSize(isLowRam
+ ? R.dimen.notification_big_picture_max_height_low_ram
+ : R.dimen.notification_big_picture_max_height);
+ int thumbnailIconWidth = mContext.getResources().getDimensionPixelSize(isLowRam
+ ? R.dimen.notification_big_picture_max_width_low_ram
+ : R.dimen.notification_big_picture_max_width);
+ return new Size(thumbnailIconWidth, thumbnailIconHeight);
+ }
+
+ /**
* Release the resources without saving the data
*/
protected void release() {
@@ -386,13 +402,14 @@ public class ScreenMediaRecorder extends MediaProjection.Callback {
public class SavedRecording {
private Uri mUri;
- private Bitmap mThumbnailBitmap;
+ private Icon mThumbnailIcon;
protected SavedRecording(Uri uri, File file, Size thumbnailSize) {
mUri = uri;
try {
- mThumbnailBitmap = ThumbnailUtils.createVideoThumbnail(
+ Bitmap thumbnailBitmap = ThumbnailUtils.createVideoThumbnail(
file, thumbnailSize, null);
+ mThumbnailIcon = Icon.createWithBitmap(thumbnailBitmap);
} catch (IOException e) {
Log.e(TAG, "Error creating thumbnail", e);
}
@@ -402,8 +419,8 @@ public class ScreenMediaRecorder extends MediaProjection.Callback {
return mUri;
}
- public @Nullable Bitmap getThumbnail() {
- return mThumbnailBitmap;
+ public @Nullable Icon getThumbnail() {
+ return mThumbnailIcon;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 00534743588c..a01ac705c0df 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -331,8 +331,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
);
collectFlow(
mWindowRootView,
- mCommunalInteractor.get().isCommunalShowing(),
- this::onCommunalShowingChanged
+ mCommunalInteractor.get().isCommunalVisible(),
+ this::onCommunalVisibleChanged
);
}
@@ -475,6 +475,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
visible = true;
mLogger.d("Visibility forced to be true");
+ } else if (state.communalVisible) {
+ visible = true;
+ mLogger.d("Visibility forced to be true by communal");
}
if (mWindowRootView != null) {
if (visible) {
@@ -510,15 +513,15 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
private void applyUserActivityTimeout(NotificationShadeWindowState state) {
- final Boolean communalShowing = state.isCommunalShowingAndNotOccluded();
+ final Boolean communalVisible = state.isCommunalVisibleAndNotOccluded();
final Boolean keyguardShowing = state.isKeyguardShowingAndNotOccluded();
long timeout = -1;
- if ((communalShowing || keyguardShowing)
+ if ((communalVisible || keyguardShowing)
&& state.statusBarState == StatusBarState.KEYGUARD
&& !state.qsExpanded) {
if (state.bouncerShowing) {
timeout = KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS;
- } else if (communalShowing) {
+ } else if (communalVisible) {
timeout = CommunalInteractor.AWAKE_INTERVAL_MS;
} else if (keyguardShowing) {
timeout = mLockScreenDisplayTimeout;
@@ -624,7 +627,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
state.dozing,
state.scrimsVisibility,
state.backgroundBlurRadius,
- state.communalShowing
+ state.communalVisible
);
}
@@ -749,8 +752,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
@VisibleForTesting
- void onCommunalShowingChanged(Boolean showing) {
- mCurrentState.communalShowing = showing;
+ void onCommunalVisibleChanged(Boolean visible) {
+ mCurrentState.communalVisible = visible;
apply(mCurrentState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index f9c9d83e03aa..e0a98b3b7f9e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -58,15 +58,15 @@ class NotificationShadeWindowState(
@JvmField var dreaming: Boolean = false,
@JvmField var scrimsVisibility: Int = 0,
@JvmField var backgroundBlurRadius: Int = 0,
- @JvmField var communalShowing: Boolean = false,
+ @JvmField var communalVisible: Boolean = false,
) {
fun isKeyguardShowingAndNotOccluded(): Boolean {
return keyguardShowing && !keyguardOccluded
}
- fun isCommunalShowingAndNotOccluded(): Boolean {
- return communalShowing && !keyguardOccluded
+ fun isCommunalVisibleAndNotOccluded(): Boolean {
+ return communalVisible && !keyguardOccluded
}
/** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */
@@ -99,7 +99,7 @@ class NotificationShadeWindowState(
dozing.toString(),
scrimsVisibility.toString(),
backgroundBlurRadius.toString(),
- communalShowing.toString(),
+ communalVisible.toString(),
)
}
@@ -140,7 +140,7 @@ class NotificationShadeWindowState(
dozing: Boolean,
scrimsVisibility: Int,
backgroundBlurRadius: Int,
- communalShowing: Boolean,
+ communalVisible: Boolean,
) {
buffer.advance().apply {
this.keyguardShowing = keyguardShowing
@@ -172,7 +172,7 @@ class NotificationShadeWindowState(
this.dozing = dozing
this.scrimsVisibility = scrimsVisibility
this.backgroundBlurRadius = backgroundBlurRadius
- this.communalShowing = communalShowing
+ this.communalVisible = communalVisible
}
}
@@ -218,7 +218,7 @@ class NotificationShadeWindowState(
"dozing",
"scrimsVisibility",
"backgroundBlurRadius",
- "communalShowing"
+ "communalVisible"
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 19a58401cbec..ef820f3dd9ae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -18,6 +18,7 @@ package com.android.systemui.shade;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.app.StatusBarManager;
@@ -477,8 +478,9 @@ public class NotificationShadeWindowViewController implements Dumpable {
if (KeyguardShadeMigrationNssl.isEnabled()) {
// When on lockscreen, if the touch originates at the top of the screen
// go directly to QS and not the shade
- if (mQuickSettingsController.shouldQuickSettingsIntercept(
- ev.getX(), ev.getY(), 0)) {
+ if (mStatusBarStateController.getState() == KEYGUARD
+ && mQuickSettingsController.shouldQuickSettingsIntercept(
+ ev.getX(), ev.getY(), 0)) {
mShadeLogger.d("NSWVC: QS intercepted");
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 84cad1d16d73..c0afa32571e7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -30,8 +30,6 @@ import androidx.lifecycle.lifecycleScope
import com.android.systemui.Flags.centralizedStatusBarDimensRefactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentService
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -59,18 +57,17 @@ internal const val INSET_DEBOUNCE_MILLIS = 500L
@SysUISingleton
class NotificationsQSContainerController @Inject constructor(
- view: NotificationsQuickSettingsContainer,
- private val navigationModeController: NavigationModeController,
- private val overviewProxyService: OverviewProxyService,
- private val shadeHeaderController: ShadeHeaderController,
- private val shadeInteractor: ShadeInteractor,
- private val fragmentService: FragmentService,
- @Main private val delayableExecutor: DelayableExecutor,
- private val featureFlags: FeatureFlags,
- private val
- notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
- private val splitShadeStateController: SplitShadeStateController,
- private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
+ view: NotificationsQuickSettingsContainer,
+ private val navigationModeController: NavigationModeController,
+ private val overviewProxyService: OverviewProxyService,
+ private val shadeHeaderController: ShadeHeaderController,
+ private val shadeInteractor: ShadeInteractor,
+ private val fragmentService: FragmentService,
+ @Main private val delayableExecutor: DelayableExecutor,
+ private val
+ notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+ private val splitShadeStateController: SplitShadeStateController,
+ private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
private var splitShadeEnabled = false
@@ -133,9 +130,6 @@ class NotificationsQSContainerController @Inject constructor(
isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
mView.setStackScroller(notificationStackScrollLayoutController.getView())
- if (featureFlags.isEnabled(Flags.QS_CONTAINER_GRAPH_OPTIMIZER)){
- mView.enableGraphOptimization()
- }
}
public override fun onViewAttached() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index de3d16a57a1f..25e558ee42dd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -32,10 +32,10 @@ import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
-import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
-import com.android.systemui.res.R;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import java.util.ArrayList;
@@ -73,6 +73,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
super(context, attrs);
+ setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH);
}
@Override
@@ -180,10 +181,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
super.dispatchDraw(canvas);
}
- void enableGraphOptimization() {
- setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH);
- }
-
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index d3459b109d79..e40bcd5dbdbc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -46,7 +46,7 @@ class ShadeExpansionStateManager @Inject constructor() {
/**
* Adds a listener that will be notified when the panel expansion fraction has changed and
- * returns the current state in a ShadeExpansionChangeEvent for legacy purposes (b/23035507).
+ * returns the current state in a ShadeExpansionChangeEvent for legacy purposes (b/281038056).
*
* @see #addExpansionListener
*/
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index a71cf950cbe1..e619806abc34 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -25,9 +25,10 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
+import com.android.systemui.util.kotlin.combine
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -44,7 +45,7 @@ class ShadeInteractorImpl
@Inject
constructor(
@Application val scope: CoroutineScope,
- deviceProvisioningRepository: DeviceProvisioningRepository,
+ deviceProvisioningInteractor: DeviceProvisioningInteractor,
disableFlagsRepository: DisableFlagsRepository,
dozeParams: DozeParameters,
keyguardRepository: KeyguardRepository,
@@ -56,7 +57,7 @@ constructor(
) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
override val isShadeEnabled: StateFlow<Boolean> =
combine(
- deviceProvisioningRepository.isFactoryResetProtectionActive,
+ deviceProvisioningInteractor.isFactoryResetProtectionActive,
disableFlagsRepository.disableFlags,
) { isFrpActive, isDisabledByFlags ->
isDisabledByFlags.isShadeEnabled() && !isFrpActive
@@ -83,7 +84,7 @@ constructor(
powerInteractor.isAsleep,
keyguardTransitionInteractor.isInTransitionToStateWhere { it == KeyguardState.AOD },
keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING },
- deviceProvisioningRepository.isFactoryResetProtectionActive,
+ deviceProvisioningInteractor.isFactoryResetProtectionActive,
) { isAsleep, goingToSleep, isPulsing, isFrpActive ->
when {
// Touches are disabled when Factory Reset Protection is active
@@ -103,7 +104,7 @@ constructor(
isShadeEnabled,
keyguardRepository.isDozing,
userSetupRepository.isUserSetUp,
- deviceProvisioningRepository.isDeviceProvisioned,
+ deviceProvisioningInteractor.isDeviceProvisioned,
) { disableFlags, isShadeEnabled, isDozing, isUserSetup, isDeviceProvisioned ->
isDeviceProvisioned &&
// Disallow QS during setup if it's a simple user switcher. (The user intends to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index ada7d3ea1698..ffb11dd3cf92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -56,6 +56,7 @@ import android.view.KeyEvent;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
+import android.view.accessibility.Flags;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -305,6 +306,13 @@ public class CommandQueue extends IStatusBar.Stub implements
default void setTopAppHidesStatusBar(boolean topAppHidesStatusBar) { }
default void addQsTile(ComponentName tile) { }
+
+ /**
+ * Add a tile to the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ * @param end if true, the tile will be added at the end. If false, at the beginning.
+ */
+ default void addQsTileToFrontOrEnd(ComponentName tile, boolean end) { }
default void remQsTile(ComponentName tile) { }
default void setQsTiles(String[] tiles) {}
@@ -904,8 +912,29 @@ public class CommandQueue extends IStatusBar.Stub implements
@Override
public void addQsTile(ComponentName tile) {
- synchronized (mLock) {
- mHandler.obtainMessage(MSG_ADD_QS_TILE, tile).sendToTarget();
+ if (Flags.a11yQsShortcut()) {
+ addQsTileToFrontOrEnd(tile, false);
+ } else {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_ADD_QS_TILE, tile).sendToTarget();
+ }
+ }
+ }
+
+ /**
+ * Add a tile to the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ * @param end if true, the tile will be added at the end. If false, at the beginning.
+ */
+ @Override
+ public void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
+ if (Flags.a11yQsShortcut()) {
+ synchronized (mLock) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = tile;
+ args.arg2 = end;
+ mHandler.obtainMessage(MSG_ADD_QS_TILE, args).sendToTarget();
+ }
}
}
@@ -1546,11 +1575,21 @@ public class CommandQueue extends IStatusBar.Stub implements
mCallbacks.get(i).showPictureInPictureMenu();
}
break;
- case MSG_ADD_QS_TILE:
- for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).addQsTile((ComponentName) msg.obj);
+ case MSG_ADD_QS_TILE: {
+ if (Flags.a11yQsShortcut()) {
+ SomeArgs someArgs = (SomeArgs) msg.obj;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).addQsTileToFrontOrEnd(
+ (ComponentName) someArgs.arg1, (boolean) someArgs.arg2);
+ }
+ someArgs.recycle();
+ } else {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).addQsTile((ComponentName) msg.obj);
+ }
}
break;
+ }
case MSG_REMOVE_QS_TILE:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).remQsTile((ComponentName) msg.obj);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index dd04531b6b34..b9afb1409d91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -617,7 +617,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private final ScrollAdapter mScrollAdapter = new ScrollAdapter() {
@Override
public boolean isScrolledToTop() {
- return mOwnScrollY == 0;
+ if (SceneContainerFlag.isEnabled()) {
+ return mController.isPlaceholderScrolledToTop();
+ } else {
+ return mOwnScrollY == 0;
+ }
}
@Override
@@ -1442,7 +1446,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
}
final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
- mAmbientState.setStackY(stackY);
+ // TODO(b/322228881): Clean up scene container vs legacy behavior in NSSL
+ if (SceneContainerFlag.isEnabled()) {
+ // stackY should be driven by scene container, not NSSL
+ mAmbientState.setStackY(mTopPadding);
+ } else {
+ mAmbientState.setStackY(stackY);
+ }
+
if (mOnStackYChanged != null) {
mOnStackYChanged.accept(listenerNeedsAnimation);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 49fde3984acc..ed266772ac15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1144,6 +1144,14 @@ public class NotificationStackScrollLayoutController implements Dumpable {
return mStackAppearanceInteractor.getStackBounds().getValue().getTop();
}
+ /**
+ * Returns whether the notification stack is scrolled to the top; i.e., it cannot be scrolled
+ * down any further.
+ */
+ public boolean isPlaceholderScrolledToTop() {
+ return mStackAppearanceInteractor.getScrolledToTop().getValue();
+ }
+
/** Set the intrinsic height of the stack content without additional padding. */
public void setIntrinsicContentHeight(float intrinsicContentHeight) {
mStackAppearanceInteractor.setIntrinsicContentHeight(intrinsicContentHeight);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index aac3c28a3426..01972646f394 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -44,4 +44,10 @@ class NotificationStackAppearanceRepository @Inject constructor() {
* screen.
*/
val contentTop = MutableStateFlow(0f)
+
+ /**
+ * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any
+ * further.
+ */
+ val scrolledToTop = MutableStateFlow(true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 1dfde09f3a85..8307397c57da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -42,10 +42,16 @@ constructor(
* notifications, this can exceed the space available on screen to show notifications, at which
* point the notification stack should become scrollable.
*/
- val intrinsicContentHeight = repository.intrinsicContentHeight.asStateFlow()
+ val intrinsicContentHeight: StateFlow<Float> = repository.intrinsicContentHeight.asStateFlow()
/** The y-coordinate in px of top of the contents of the notification stack. */
- val contentTop = repository.contentTop.asStateFlow()
+ val contentTop: StateFlow<Float> = repository.contentTop.asStateFlow()
+
+ /**
+ * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any
+ * further.
+ */
+ val scrolledToTop: StateFlow<Boolean> = repository.scrolledToTop.asStateFlow()
/** Sets the position of the notification stack in the current scene. */
fun setStackBounds(bounds: NotificationContainerBounds) {
@@ -62,4 +68,9 @@ constructor(
fun setContentTop(startY: Float) {
repository.contentTop.value = startY
}
+
+ /** Sets whether the notification stack is scrolled to the top. */
+ fun setScrolledToTop(scrolledToTop: Boolean) {
+ repository.scrolledToTop.value = scrolledToTop
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index ed15f557fb39..6c2cbbecb477 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -25,7 +25,6 @@ import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
-import kotlin.math.pow
import kotlin.math.roundToInt
import kotlinx.coroutines.launch
@@ -65,7 +64,9 @@ object NotificationStackAppearanceViewBinder {
viewModel.expandFraction.collect { expandFraction ->
ambientState.expansionFraction = expandFraction
controller.expandedHeight = expandFraction * controller.view.height
- controller.setMaxAlphaForExpansion(expandFraction.pow(0.75f))
+ controller.setMaxAlphaForExpansion(
+ ((expandFraction - 0.5f) / 0.5f).coerceAtLeast(0f)
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index 74db5831f7f8..56ff7f9e50df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -19,11 +19,15 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
/** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
@SysUISingleton
@@ -32,9 +36,40 @@ class NotificationStackAppearanceViewModel
constructor(
stackAppearanceInteractor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
+ sceneInteractor: SceneInteractor,
) {
- /** The expansion fraction from the top of the notification shade. */
- val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion
+ /**
+ * The expansion fraction of the notification stack. It should go from 0 to 1 when transitioning
+ * from Gone to Shade scenes, and remain at 1 when in Lockscreen or Shade scenes and while
+ * transitioning from Shade to QuickSettings scenes.
+ */
+ val expandFraction: Flow<Float> =
+ combine(
+ shadeInteractor.shadeExpansion,
+ sceneInteractor.transitionState,
+ ) { shadeExpansion, transitionState ->
+ when (transitionState) {
+ is ObservableTransitionState.Idle -> {
+ if (transitionState.scene == SceneKey.Lockscreen) {
+ 1f
+ } else {
+ shadeExpansion
+ }
+ }
+ is ObservableTransitionState.Transition -> {
+ if (
+ (transitionState.fromScene == SceneKey.Shade &&
+ transitionState.toScene == SceneKey.QuickSettings) ||
+ (transitionState.fromScene == SceneKey.QuickSettings &&
+ transitionState.toScene == SceneKey.Shade)
+ ) {
+ 1f
+ } else {
+ shadeExpansion
+ }
+ }
+ }
+ }
/** The bounds of the notification stack in the current scene. */
val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 385f0619288d..a436f1783a0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -87,4 +87,9 @@ constructor(
fun onContentTopChanged(padding: Float) {
interactor.setContentTop(padding)
}
+
+ /** Sets whether the notification stack is scrolled to the top. */
+ fun setScrolledToTop(scrolledToTop: Boolean) {
+ interactor.setScrolledToTop(scrolledToTop)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 4617ce49f44a..3915c3763a41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -24,13 +24,24 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
@@ -51,6 +62,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
@@ -69,43 +81,52 @@ constructor(
communalInteractor: CommunalInteractor,
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
+ dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
private val aodBurnInViewModel: AodBurnInViewModel,
) {
- private val statesForConstrainedNotifications =
- setOf(
- KeyguardState.AOD,
- KeyguardState.LOCKSCREEN,
- KeyguardState.DOZING,
- KeyguardState.ALTERNATE_BOUNCER,
- KeyguardState.PRIMARY_BOUNCER
+ private val statesForConstrainedNotifications: Set<KeyguardState> =
+ setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
+
+ private val edgeToAlphaViewModel =
+ mapOf<Edge?, Flow<Float>>(
+ Edge(from = LOCKSCREEN, to = DREAMING) to
+ lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
+ Edge(from = DREAMING, to = LOCKSCREEN) to
+ dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
+ Edge(from = LOCKSCREEN, to = OCCLUDED) to
+ lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
+ Edge(from = OCCLUDED, to = LOCKSCREEN) to
+ occludedToLockscreenTransitionViewModel.lockscreenAlpha,
)
- private val lockscreenToOccludedRunning =
- keyguardTransitionInteractor
- .transition(KeyguardState.LOCKSCREEN, KeyguardState.OCCLUDED)
- .map { it.transitionState == STARTED || it.transitionState == RUNNING }
+ private val lockscreenTransitionInProgress: Flow<Edge?> =
+ keyguardTransitionInteractor.transitions
+ .map { step ->
+ if (
+ (step.transitionState == STARTED || step.transitionState == RUNNING) &&
+ (step.from == LOCKSCREEN || step.to == LOCKSCREEN)
+ ) {
+ Edge(step.from, step.to)
+ } else {
+ null
+ }
+ }
.distinctUntilChanged()
- .onStart { emit(false) }
-
- private val occludedToLockscreenRunning =
- keyguardTransitionInteractor
- .transition(KeyguardState.OCCLUDED, KeyguardState.LOCKSCREEN)
- .map { it.transitionState == STARTED || it.transitionState == RUNNING }
- .distinctUntilChanged()
- .onStart { emit(false) }
+ .onStart { emit(null) }
private val lockscreenToGlanceableHubRunning =
keyguardTransitionInteractor
- .transition(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+ .transition(LOCKSCREEN, GLANCEABLE_HUB)
.map { it.transitionState == STARTED || it.transitionState == RUNNING }
.distinctUntilChanged()
.onStart { emit(false) }
private val glanceableHubToLockscreenRunning =
keyguardTransitionInteractor
- .transition(KeyguardState.GLANCEABLE_HUB, KeyguardState.LOCKSCREEN)
+ .transition(GLANCEABLE_HUB, LOCKSCREEN)
.map { it.transitionState == STARTED || it.transitionState == RUNNING }
.distinctUntilChanged()
.onStart { emit(false) }
@@ -141,7 +162,7 @@ constructor(
statesForConstrainedNotifications.contains(it)
},
keyguardTransitionInteractor
- .transitionValue(KeyguardState.LOCKSCREEN)
+ .transitionValue(LOCKSCREEN)
.onStart { emit(0f) }
.map { it > 0 }
) { constrainedNotificationState, transitioningToOrFromLockscreen ->
@@ -242,38 +263,46 @@ constructor(
initialValue = NotificationContainerBounds(),
)
+ /** As QS is expanding, fade out notifications unless in splitshade */
+ private val alphaForQsExpansion: Flow<Float> =
+ interactor.configurationBasedDimensions.flatMapLatest {
+ if (it.useSplitShade) {
+ flowOf(1f)
+ } else {
+ shadeInteractor.qsExpansion.map { 1f - it }
+ }
+ }
+
val expansionAlpha: Flow<Float> =
// Due to issues with the legacy shade, some shade expansion events are sent incorrectly,
- // such as when the shade resets. This can happen while the LOCKSCREEN<->OCCLUDED transition
+ // such as when the shade resets. This can happen while the transition to/from LOCKSCREEN
// is running. Therefore use a series of flatmaps to prevent unwanted interruptions while
// those transitions are in progress. Without this, the alpha value will produce a visible
// flicker.
- lockscreenToOccludedRunning.flatMapLatest { isLockscreenToOccludedRunning ->
- if (isLockscreenToOccludedRunning) {
- lockscreenToOccludedTransitionViewModel.lockscreenAlpha
- } else {
- occludedToLockscreenRunning.flatMapLatest { isOccludedToLockscreenRunning ->
- if (isOccludedToLockscreenRunning) {
- occludedToLockscreenTransitionViewModel.lockscreenAlpha.onStart { emit(0f) }
- } else {
+ lockscreenTransitionInProgress
+ .flatMapLatest { edge ->
+ edgeToAlphaViewModel.getOrElse(
+ edge,
+ {
isOnLockscreenWithoutShade.flatMapLatest { isOnLockscreenWithoutShade ->
combineTransform(
keyguardInteractor.keyguardAlpha,
shadeCollpaseFadeIn,
- ) { alpha, shadeCollpaseFadeIn ->
+ alphaForQsExpansion,
+ ) { alpha, shadeCollpaseFadeIn, alphaForQsExpansion ->
if (isOnLockscreenWithoutShade) {
if (!shadeCollpaseFadeIn) {
emit(alpha)
}
} else {
- emit(1f)
+ emit(alphaForQsExpansion)
}
}
}
}
- }
+ )
}
- }
+ .distinctUntilChanged()
/**
* Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB transition
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 60a4606ef0d0..39ca7b219663 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -179,6 +179,11 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
}
@Override
+ public void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
+ mQSHost.addTile(tile, end);
+ }
+
+ @Override
public void remQsTile(ComponentName tile) {
mQSHost.removeTileByUser(tile);
}
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 0051161eff35..1c33d3fd0288 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
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
import javax.inject.Inject
@@ -40,6 +41,7 @@ class DeviceBasedSatelliteViewModel
constructor(
interactor: DeviceBasedSatelliteInteractor,
@Application scope: CoroutineScope,
+ airplaneModeRepository: AirplaneModeRepository,
) {
private val shouldShowIcon: StateFlow<Boolean> =
interactor.areAllConnectionsOutOfService
@@ -47,7 +49,11 @@ constructor(
if (!allOos) {
flowOf(false)
} else {
- interactor.isSatelliteAllowed
+ combine(interactor.isSatelliteAllowed, airplaneModeRepository.isAirplaneMode) {
+ isSatelliteAllowed,
+ isAirplaneMode ->
+ isSatelliteAllowed && !isAirplaneMode
+ }
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index 3c4ca4465874..b0192c0f5ee5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -22,6 +22,7 @@ import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.Trace;
+import android.service.notification.StatusBarNotification;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
@@ -46,8 +47,9 @@ public class SensitiveNotificationProtectionControllerImpl
public void onStart(MediaProjectionInfo info) {
Trace.beginSection(
"SNPC.onProjectionStart");
- mProjection = info;
- mListeners.forEach(Runnable::run);
+ // Only enable sensitive content protection if sharing full screen
+ // Launch cookie only set (non-null) if sharing single app/task
+ updateProjectionState((info.getLaunchCookie() == null) ? info : null);
Trace.endSection();
}
@@ -55,10 +57,22 @@ public class SensitiveNotificationProtectionControllerImpl
public void onStop(MediaProjectionInfo info) {
Trace.beginSection(
"SNPC.onProjectionStop");
- mProjection = null;
- mListeners.forEach(Runnable::run);
+ updateProjectionState(null);
Trace.endSection();
}
+
+ private void updateProjectionState(MediaProjectionInfo info) {
+ // capture previous state
+ boolean wasSensitive = isSensitiveStateActive();
+
+ // update internal state
+ mProjection = info;
+
+ // if either previous or new state is sensitive, notify listeners.
+ if (wasSensitive || isSensitiveStateActive()) {
+ mListeners.forEach(Runnable::run);
+ }
+ }
};
@Inject
@@ -86,7 +100,6 @@ public class SensitiveNotificationProtectionControllerImpl
public boolean isSensitiveStateActive() {
// TODO(b/316955558): Add disabled by developer option
// TODO(b/316955306): Add feature exemption for sysui and bug handlers
- // TODO(b/316955346): Add feature exemption for single app screen sharing
return mProjection != null;
}
@@ -96,9 +109,18 @@ public class SensitiveNotificationProtectionControllerImpl
return false;
}
+ MediaProjectionInfo projection = mProjection;
+ if (projection == null) {
+ return false;
+ }
+
// Exempt foreground service notifications from protection in effort to keep screen share
// stop actions easily accessible
- // TODO(b/316955208): Exempt FGS notifications only for app that started projection
- return !entry.getSbn().getNotification().isFgsOrUij();
+ StatusBarNotification sbn = entry.getSbn();
+ if (sbn.getNotification().isFgsOrUij()) {
+ return !sbn.getPackageName().equals(projection.getPackageName());
+ }
+
+ return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
new file mode 100644
index 000000000000..32cf86d8e390
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.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.statusbar.policy.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates device-provisioning related business logic. */
+@SysUISingleton
+class DeviceProvisioningInteractor
+@Inject
+constructor(
+ repository: DeviceProvisioningRepository,
+) {
+ /**
+ * Whether this device has been provisioned.
+ *
+ * @see android.provider.Settings.Global.DEVICE_PROVISIONED
+ */
+ val isDeviceProvisioned: Flow<Boolean> = repository.isDeviceProvisioned
+
+ /** Whether Factory Reset Protection (FRP) is currently active, locking the device. */
+ val isFactoryResetProtectionActive: Flow<Boolean> = repository.isFactoryResetProtectionActive
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index cf76c0d2e696..37be1c6aa73d 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.user.data.repository
+import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.UserInfo
import android.os.UserHandle
@@ -209,18 +210,15 @@ constructor(
override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
+ @SuppressLint("MissingPermission")
override fun refreshUsers() {
applicationScope.launch {
- val result = withContext(backgroundDispatcher) { manager.aliveUsers }
-
- if (result != null) {
- _userInfos.value =
- result
- // Users should be sorted by ascending creation time.
- .sortedBy { it.creationTime }
- // The guest user is always last, regardless of creation time.
- .sortedBy { it.isGuest }
- }
+ _userInfos.value =
+ withContext(backgroundDispatcher) { manager.aliveUsers }
+ // Users should be sorted by ascending creation time.
+ .sortedBy { it.creationTime }
+ // The guest user is always last, regardless of creation time.
+ .sortedBy { it.isGuest }
if (mainUserId == UserHandle.USER_NULL) {
val mainUser = withContext(backgroundDispatcher) { manager.mainUser }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index 8fe57e116405..d47413faeadf 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -20,21 +20,19 @@ import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.time.SystemClockImpl
import java.util.concurrent.atomic.AtomicReference
import kotlin.math.max
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/**
@@ -106,6 +104,14 @@ fun <S, T : S> Flow<T>.pairwise(initialValue: S): Flow<WithPrev<S, T>> =
/** Holds a [newValue] emitted from a [Flow], along with the [previousValue] emitted value. */
data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
+/** Emits a [Unit] only when the number of downstream subscribers of this flow increases. */
+fun <T> MutableSharedFlow<T>.onSubscriberAdded(): Flow<Unit> {
+ return subscriptionCount
+ .pairwise(initialValue = 0)
+ .filter { (previous, current) -> current > previous }
+ .map {}
+}
+
/**
* Returns a new [Flow] that combines the [Set] changes between each emission from [this] using
* [transform].
@@ -183,34 +189,6 @@ fun <A> Flow<*>.sample(other: Flow<A>): Flow<A> = sample(other) { _, a -> a }
/**
* Returns a flow that mirrors the original flow, but delays values following emitted values for the
- * given [periodMs]. If the original flow emits more than one value during this period, only the
- * latest value is emitted.
- *
- * Example:
- * ```kotlin
- * flow {
- * emit(1) // t=0ms
- * delay(90)
- * emit(2) // t=90ms
- * delay(90)
- * emit(3) // t=180ms
- * delay(1010)
- * emit(4) // t=1190ms
- * delay(1010)
- * emit(5) // t=2200ms
- * }.throttle(1000)
- * ```
- *
- * produces the following emissions at the following times
- *
- * ```text
- * 1 (t=0ms), 3 (t=1000ms), 4 (t=2000ms), 5 (t=3000ms)
- * ```
- */
-fun <T> Flow<T>.throttle(periodMs: Long): Flow<T> = this.throttle(periodMs, SystemClockImpl())
-
-/**
- * Returns a flow that mirrors the original flow, but delays values following emitted values for the
* given [periodMs] as reported by the given [clock]. If the original flow emits more than one value
* during this period, only The latest value is emitted.
*
@@ -235,70 +213,37 @@ fun <T> Flow<T>.throttle(periodMs: Long): Flow<T> = this.throttle(periodMs, Syst
* 1 (t=0ms), 3 (t=1000ms), 4 (t=2000ms), 5 (t=3000ms)
* ```
*/
-fun <T> Flow<T>.throttle(periodMs: Long, clock: SystemClock): Flow<T> = channelFlow {
- coroutineScope {
- var previousEmitTimeMs = 0L
- var delayJob: Job? = null
- var sendJob: Job? = null
- val outerScope = this
+fun <T> Flow<T>.throttle(periodMs: Long, clock: SystemClock = SystemClockImpl()): Flow<T> =
+ channelFlow {
+ coroutineScope {
+ var previousEmitTimeMs = 0L
+ var delayJob: Job? = null
+ var sendJob: Job? = null
+ val outerScope = this
- collect {
- delayJob?.cancel()
- sendJob?.join()
- val currentTimeMs = clock.elapsedRealtime()
- val timeSinceLastEmit = currentTimeMs - previousEmitTimeMs
- val timeUntilNextEmit = max(0L, periodMs - timeSinceLastEmit)
- if (timeUntilNextEmit > 0L) {
- // We create delayJob to allow cancellation during the delay period
- delayJob = launch {
- delay(timeUntilNextEmit)
- sendJob =
- outerScope.launch(start = CoroutineStart.UNDISPATCHED) {
- send(it)
- previousEmitTimeMs = clock.elapsedRealtime()
- }
+ collect {
+ delayJob?.cancel()
+ sendJob?.join()
+ val currentTimeMs = clock.elapsedRealtime()
+ val timeSinceLastEmit = currentTimeMs - previousEmitTimeMs
+ val timeUntilNextEmit = max(0L, periodMs - timeSinceLastEmit)
+ if (timeUntilNextEmit > 0L) {
+ // We create delayJob to allow cancellation during the delay period
+ delayJob = launch {
+ delay(timeUntilNextEmit)
+ sendJob =
+ outerScope.launch(start = CoroutineStart.UNDISPATCHED) {
+ send(it)
+ previousEmitTimeMs = clock.elapsedRealtime()
+ }
+ }
+ } else {
+ send(it)
+ previousEmitTimeMs = currentTimeMs
}
- } else {
- send(it)
- previousEmitTimeMs = currentTimeMs
}
}
}
-}
-
-/**
- * Returns a [StateFlow] launched in the surrounding [CoroutineScope]. This [StateFlow] gets its
- * value by invoking [getValue] whenever an event is emitted from [changedSignals]. It will also
- * immediately invoke [getValue] to establish its initial value.
- */
-inline fun <T> CoroutineScope.stateFlow(
- changedSignals: Flow<*>,
- crossinline getValue: () -> T,
-): StateFlow<T> =
- changedSignals.map { getValue() }.stateIn(this, SharingStarted.Eagerly, getValue())
-
-inline fun <T1, T2, T3, T4, T5, T6, R> combine(
- flow: Flow<T1>,
- flow2: Flow<T2>,
- flow3: Flow<T3>,
- flow4: Flow<T4>,
- flow5: Flow<T5>,
- flow6: Flow<T6>,
- crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
-): Flow<R> {
- return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*>
- ->
- @Suppress("UNCHECKED_CAST")
- transform(
- args[0] as T1,
- args[1] as T2,
- args[2] as T3,
- args[3] as T4,
- args[4] as T5,
- args[5] as T6
- )
- }
-}
inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
flow: Flow<T1>,
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index a13d85bf272c..cabe831c5964 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -25,9 +25,13 @@ import dagger.Provides
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.newFixedThreadPoolContext
import kotlinx.coroutines.plus
+private const val LIMIT_BACKGROUND_DISPATCHER_THREADS = true
+
/** Providers for various SystemIU specific coroutines-related constructs. */
@Module
class SysUICoroutinesModule {
@@ -40,14 +44,13 @@ class SysUICoroutinesModule {
): CoroutineScope = applicationScope.plus(coroutineContext)
/**
- * Provide a [CoroutineDispatcher] backed by a thread pool containing at most X threads, where X
- * is the number of CPU cores available.
- *
- * Because there are multiple threads at play, there is no serialization order guarantee. You
- * should use a [kotlinx.coroutines.channels.Channel] for serialization if necessary.
+ * Default Coroutine dispatcher for background operations.
*
- * @see Dispatchers.Default
+ * Note that this is explicitly limiting the number of threads. In the past, we used
+ * [Dispatchers.IO]. This caused >40 threads to be spawned, and a lot of thread list lock
+ * contention between then, eventually causing jank.
*/
+ @OptIn(DelicateCoroutinesApi::class)
@Provides
@SysUISingleton
@Background
@@ -55,12 +58,29 @@ class SysUICoroutinesModule {
"Use @Background CoroutineContext instead",
ReplaceWith("bgCoroutineContext()", "kotlin.coroutines.CoroutineContext")
)
- fun bgDispatcher(): CoroutineDispatcher = Dispatchers.IO
+ fun bgDispatcher(): CoroutineDispatcher {
+ return if (LIMIT_BACKGROUND_DISPATCHER_THREADS) {
+ // Why a new ThreadPool instead of just using Dispatchers.IO with
+ // CoroutineDispatcher.limitedParallelism? Because, if we were to use Dispatchers.IO, we
+ // would share those threads with other dependencies using Dispatchers.IO.
+ // Using a dedicated thread pool we have guarantees only SystemUI is able to schedule
+ // code on those.
+ newFixedThreadPoolContext(
+ nThreads = Runtime.getRuntime().availableProcessors(),
+ name = "SystemUIBg"
+ )
+ } else {
+ Dispatchers.IO
+ }
+ }
@Provides
@Background
@SysUISingleton
- fun bgCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
- return Dispatchers.IO + tracingCoroutineContext
+ fun bgCoroutineContext(
+ @Tracing tracingCoroutineContext: CoroutineContext,
+ @Background bgCoroutineDispatcher: CoroutineDispatcher,
+ ): CoroutineContext {
+ return bgCoroutineDispatcher + tracingCoroutineContext
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 1e801aeb5a29..e8325065c219 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -347,10 +347,10 @@ public final class WMShell implements
desktopMode.addVisibleTasksListener(
new DesktopModeTaskRepository.VisibleTasksListener() {
@Override
- public void onVisibilityChanged(int displayId, boolean hasFreeformTasks) {
+ public void onTasksVisibilityChanged(int displayId, int visibleTasksCount) {
if (displayId == Display.DEFAULT_DISPLAY) {
mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE,
- hasFreeformTasks)
+ visibleTasksCount > 0)
.commitUpdate(mDisplayTracker.getDefaultDisplayId());
}
// TODO(b/278084491): update sysui state for changes on other displays
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 8299acbc2d52..375ebe86ae29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -51,6 +51,7 @@ import android.view.WindowManagerGlobal;
import android.view.accessibility.IRemoteMagnificationAnimationCallback;
import android.view.animation.AccelerateInterpolator;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -79,6 +80,7 @@ import java.util.function.Supplier;
@LargeTest
@RunWith(AndroidTestingRunner.class)
+@FlakyTest(bugId = 308501761)
public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Rule
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index b1e471af2e71..fc3425567919 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -1,3 +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 com.android.systemui.biometrics.domain.interactor
import android.hardware.biometrics.AuthenticateOptions
@@ -5,24 +21,19 @@ import android.hardware.biometrics.IBiometricContextListener
import android.hardware.biometrics.IBiometricContextListener.FoldState
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.AuthController
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.fakeDeviceStateRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
-import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
-import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
-import com.android.systemui.unfold.updates.FoldStateProvider
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -30,8 +41,6 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@OptIn(ExperimentalCoroutinesApi::class)
@@ -41,31 +50,20 @@ class LogContextInteractorImplTest : SysuiTestCase() {
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
- private val testScope = TestScope()
-
- @Mock private lateinit var foldProvider: FoldStateProvider
- @Mock private lateinit var authController: AuthController
- @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
-
- private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
- private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val deviceStateRepository = kosmos.fakeDeviceStateRepository
+ private val udfpsOverlayInteractor = kosmos.udfpsOverlayInteractor
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private lateinit var interactor: LogContextInteractorImpl
@Before
fun setup() {
- keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- udfpsOverlayInteractor =
- UdfpsOverlayInteractor(
- context,
- authController,
- selectedUserInteractor,
- testScope.backgroundScope,
- )
interactor =
LogContextInteractorImpl(
testScope.backgroundScope,
- foldProvider,
+ deviceStateRepository,
KeyguardTransitionInteractorFactory.create(
repository = keyguardTransitionRepository,
scope = testScope.backgroundScope,
@@ -189,33 +187,31 @@ class LogContextInteractorImplTest : SysuiTestCase() {
@Test
fun foldStateChanges() =
testScope.runTest {
- val foldState = collectLastValue(interactor.foldState)
- runCurrent()
- val listener = foldProvider.captureListener()
+ val foldState by collectLastValue(interactor.foldState)
+
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.HALF_FOLDED)
+ assertThat(foldState).isEqualTo(FoldState.HALF_OPENED)
- listener.onFoldUpdate(FOLD_UPDATE_START_OPENING)
- assertThat(foldState()).isEqualTo(FoldState.UNKNOWN)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.CONCURRENT_DISPLAY)
+ assertThat(foldState).isEqualTo(FoldState.FULLY_OPENED)
- listener.onFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
- assertThat(foldState()).isEqualTo(FoldState.HALF_OPENED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.UNFOLDED)
+ assertThat(foldState).isEqualTo(FoldState.FULLY_OPENED)
- listener.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
- assertThat(foldState()).isEqualTo(FoldState.FULLY_OPENED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.FOLDED)
+ assertThat(foldState).isEqualTo(FoldState.FULLY_CLOSED)
- listener.onFoldUpdate(FOLD_UPDATE_START_CLOSING)
- assertThat(foldState()).isEqualTo(FoldState.FULLY_OPENED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.REAR_DISPLAY)
+ assertThat(foldState).isEqualTo(FoldState.FULLY_OPENED)
- listener.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
- assertThat(foldState()).isEqualTo(FoldState.FULLY_CLOSED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.UNKNOWN)
+ assertThat(foldState).isEqualTo(FoldState.UNKNOWN)
}
@Test
fun contextSubscriberChanges() =
testScope.runTest {
- runCurrent()
- val foldListener = foldProvider.captureListener()
- foldListener.onFoldUpdate(FOLD_UPDATE_START_CLOSING)
- foldListener.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.FOLDED)
keyguardTransitionRepository.startTransitionTo(KeyguardState.AOD)
var folded: Int? = null
@@ -243,8 +239,7 @@ class LogContextInteractorImplTest : SysuiTestCase() {
assertThat(displayState).isEqualTo(AuthenticateOptions.DISPLAY_STATE_AOD)
assertThat(ignoreTouches).isFalse()
- foldListener.onFoldUpdate(FOLD_UPDATE_START_OPENING)
- foldListener.onFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.HALF_FOLDED)
keyguardTransitionRepository.startTransitionTo(KeyguardState.LOCKSCREEN)
runCurrent()
@@ -259,7 +254,7 @@ class LogContextInteractorImplTest : SysuiTestCase() {
job.cancel()
// stale updates should be ignored
- foldListener.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+ deviceStateRepository.emit(DeviceStateRepository.DeviceState.UNFOLDED)
keyguardTransitionRepository.startTransitionTo(KeyguardState.AOD)
runCurrent()
@@ -270,8 +265,3 @@ class LogContextInteractorImplTest : SysuiTestCase() {
private suspend fun FakeKeyguardTransitionRepository.startTransitionTo(newState: KeyguardState) =
sendTransitionStep(TransitionStep(to = newState, transitionState = TransitionState.STARTED))
-
-private fun FoldStateProvider.captureListener() =
- withArgCaptor<FoldStateProvider.FoldUpdatesListener> {
- verify(this@captureListener).addCallback(capture())
- }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index 9c5cd713dffb..20dd913550d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -77,6 +77,18 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
}
@Test
+ fun deleteWidget_notInDb_returnsFalse() =
+ testScope.runTest {
+ val (widgetId, provider, priority) = widgetInfo1
+ communalWidgetDao.addWidget(
+ widgetId = widgetId,
+ provider = provider,
+ priority = priority,
+ )
+ assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse()
+ }
+
+ @Test
fun addWidget_emitsActiveWidgetsInDb(): Unit =
testScope.runTest {
val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index 9fe40d73fa1d..8b16da2a1f78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
@@ -46,6 +47,10 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() {
private lateinit var underTest: KeyguardBlueprintInteractor
private lateinit var testScope: TestScope
+ val refreshBluePrint: MutableSharedFlow<Unit> = MutableSharedFlow(extraBufferCapacity = 1)
+ val refreshBlueprintTransition: MutableSharedFlow<IntraBlueprintTransitionType> =
+ MutableSharedFlow(extraBufferCapacity = 1)
+
@Mock private lateinit var splitShadeStateController: SplitShadeStateController
@Mock private lateinit var keyguardBlueprintRepository: KeyguardBlueprintRepository
@@ -54,6 +59,9 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
testScope = TestScope(StandardTestDispatcher())
whenever(keyguardBlueprintRepository.configurationChange).thenReturn(configurationFlow)
+ whenever(keyguardBlueprintRepository.refreshBluePrint).thenReturn(refreshBluePrint)
+ whenever(keyguardBlueprintRepository.refreshBlueprintTransition)
+ .thenReturn(refreshBlueprintTransition)
underTest =
KeyguardBlueprintInteractor(
@@ -105,4 +113,11 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() {
underTest.transitionToBlueprint("abc")
verify(keyguardBlueprintRepository).applyBlueprint("abc")
}
+
+ @Test
+ fun testRefreshBlueprintWithTransition() {
+ underTest.refreshBlueprintWithTransition(IntraBlueprintTransitionType.DefaultTransition)
+ verify(keyguardBlueprintRepository)
+ .refreshBlueprintWithTransition(IntraBlueprintTransitionType.DefaultTransition)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index e93ad0be3e85..28127186706b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1895,6 +1895,37 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
coroutineContext.cancelChildren()
}
+ @Test
+ fun glanceableHubToDreaming() =
+ testScope.runTest {
+ // GIVEN a device that is not dreaming or dozing
+ keyguardRepository.setDreamingWithOverlay(false)
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ runCurrent()
+
+ // GIVEN a prior transition has run to GLANCEABLE_HUB
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+
+ // WHEN the device begins to dream
+ keyguardRepository.setDreamingWithOverlay(true)
+ advanceUntilIdle()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DREAMING should occur
+ assertThat(info.ownerName)
+ .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+ assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+ assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
private fun createKeyguardInteractor(): KeyguardInteractor {
return KeyguardInteractorFactory.create(
featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index acb6ff0192e8..2da74b099eda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -165,7 +165,7 @@ class ClockSectionTest : SysuiTestCase() {
val cs = ConstraintSet()
underTest.applyDefaultConstraints(cs)
- val expectedLargeClockTopMargin = LARGE_CLOCK_TOP - CLOCK_FADE_TRANSLATION_Y
+ val expectedLargeClockTopMargin = LARGE_CLOCK_TOP
assetLargeClockTop(cs, expectedLargeClockTopMargin)
val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_SPLIT_SHADE
@@ -178,7 +178,7 @@ class ClockSectionTest : SysuiTestCase() {
setSplitShade(false)
val cs = ConstraintSet()
underTest.applyDefaultConstraints(cs)
- val expectedLargeClockTopMargin = LARGE_CLOCK_TOP - CLOCK_FADE_TRANSLATION_Y
+ val expectedLargeClockTopMargin = LARGE_CLOCK_TOP
assetLargeClockTop(cs, expectedLargeClockTopMargin)
val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_NON_SPLIT_SHADE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
index 437a35f2fab5..e3c4c2858b3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
@@ -475,10 +475,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
// Then the device name is the PhoneMediaDevice string
val data = captureDeviceData(KEY)
- assertThat(data.name)
- .isEqualTo(
- context.getString(com.android.settingslib.R.string.media_transfer_this_device_name)
- )
+ assertThat(data.name).isEqualTo(PhoneMediaDevice.getMediaTransferThisDeviceName(context))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index ba7927d6b8bd..edba902d5886 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.dream.MediaDreamComplication
@@ -52,8 +53,6 @@ import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertNotNull
@@ -106,8 +105,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
private lateinit var dreamOverlayCallback:
ArgumentCaptor<(DreamOverlayStateController.Callback)>
@JvmField @Rule val mockito = MockitoJUnit.rule()
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val testScope = kosmos.testScope
private lateinit var mediaHierarchyManager: MediaHierarchyManager
private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
private lateinit var mediaFrame: ViewGroup
@@ -532,6 +530,58 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
}
@Test
+ fun testCommunalLocation_showsOverLockscreen() =
+ testScope.runTest {
+ // Device is on lock screen.
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ // UMO goes to communal even over the lock screen.
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ runCurrent()
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
+ nullable(),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ }
+
+ @Test
+ fun testCommunalLocation_showsUntilQsExpands() =
+ testScope.runTest {
+ // Device is on lock screen.
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ runCurrent()
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
+ nullable(),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ clearInvocations(mediaCarouselController)
+
+ // Start opening the shade.
+ mediaHierarchyManager.qsExpansion = 0.1f
+ runCurrent()
+
+ // UMO goes to the shade instead.
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_QS),
+ any(MediaHostState::class.java),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ }
+
+ @Test
fun testQsExpandedChanged_noQqsMedia() {
// When we are looking at QQS with active media
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index a92111e2c270..da8d29c622d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -246,8 +246,9 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
@Test
- public void testShouldUzeHorizontalLayout_falseForSplitShade() {
+ public void testShouldUseHorizontalLayout_falseForSplitShade() {
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES;
when(mMediaHost.getVisible()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(false);
@@ -270,12 +271,13 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
}
@Test
- public void testChangeConfiguration_shouldUseHorizontalLayout() {
+ public void testChangeConfiguration_shouldUseHorizontalLayoutInLandscape_true() {
when(mMediaHost.getVisible()).thenReturn(true);
mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
- // When device is rotated to landscape
+ // When device is rotated to landscape and is long
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES;
mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
// Then the layout changes
@@ -292,6 +294,29 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
}
@Test
+ public void testChangeConfiguration_shouldUseHorizontalLayoutInLongDevices_true() {
+ when(mMediaHost.getVisible()).thenReturn(true);
+ mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
+
+ // When device is rotated to landscape and is long
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES;
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+
+ // Then the layout changes
+ assertThat(mController.shouldUseHorizontalLayout()).isTrue();
+ verify(mHorizontalLayoutListener).run();
+
+ // When device changes to not-long
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_NO;
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+
+ // Then the layout changes back
+ assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+ verify(mHorizontalLayoutListener, times(2)).run();
+ }
+
+ @Test
public void testRefreshAllTilesDoesntRefreshListeningTiles() {
when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile));
mController.setTiles();
@@ -310,6 +335,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
when(mMediaHost.getVisible()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(false);
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES;
mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
assertThat(mController.shouldUseHorizontalLayout()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index a6e240b1b701..08319719bee6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -44,6 +44,7 @@ import org.junit.Assert.assertThrows
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.BeforeClass
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -51,6 +52,7 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@Ignore("b/323053208")
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index b3e386e69905..cc27cbd9d809 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -189,7 +189,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
@@ -411,7 +410,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- new FakeDeviceProvisioningRepository(),
+ mKosmos.getDeviceProvisioningInteractor(),
new FakeDisableFlagsRepository(),
mDozeParameters,
mFakeKeyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 461db8e6166c..7f4508a11c0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -99,7 +99,6 @@ import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
@@ -260,7 +259,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- new FakeDeviceProvisioningRepository(),
+ mKosmos.getDeviceProvisioningInteractor(),
new FakeDisableFlagsRepository(),
mock(DozeParameters.class),
keyguardRepository,
@@ -452,11 +451,11 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
}
@Test
- public void setCommunalShowing_userTimeout() {
+ public void setCommunalVisible_userTimeout() {
setKeyguardShowing();
clearInvocations(mWindowManager);
- mNotificationShadeWindowController.onCommunalShowingChanged(true);
+ mNotificationShadeWindowController.onCommunalVisibleChanged(true);
verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
assertThat(mLayoutParameters.getValue().userActivityTimeout)
.isEqualTo(CommunalInteractor.AWAKE_INTERVAL_MS);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 697b05aa9add..c2261211b339 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -28,8 +28,6 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
@@ -94,7 +92,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
lateinit var underTest: NotificationsQSContainerController
- private lateinit var featureFlags: FakeFeatureFlags
private lateinit var navigationModeCallback: ModeChangedListener
private lateinit var taskbarVisibilityCallback: OverviewProxyListener
private lateinit var windowInsetsCallback: Consumer<WindowInsets>
@@ -106,7 +103,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
- featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, false) }
mContext.ensureTestableResources()
whenever(view.context).thenReturn(mContext)
whenever(view.resources).thenReturn(mContext.resources)
@@ -123,7 +119,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
@@ -536,7 +531,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index e66251a030a3..c32635020ddc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -28,8 +28,6 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
@@ -91,7 +89,6 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
lateinit var underTest: NotificationsQSContainerController
- private lateinit var featureFlags: FakeFeatureFlags
private lateinit var navigationModeCallback: ModeChangedListener
private lateinit var taskbarVisibilityCallback: OverviewProxyListener
private lateinit var windowInsetsCallback: Consumer<WindowInsets>
@@ -104,7 +101,6 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME)
- featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, true) }
mContext.ensureTestableResources()
whenever(view.context).thenReturn(mContext)
whenever(view.resources).thenReturn(mContext.resources)
@@ -122,7 +118,6 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
@@ -513,7 +508,6 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 3e0a647d464e..7bd9d92baf59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -103,7 +103,6 @@ import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
@@ -205,9 +204,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase {
mStatusBarStateController = mKosmos.getStatusBarStateController();
mInteractionJankMonitor = mKosmos.getInteractionJankMonitor();
- FakeDeviceProvisioningRepository deviceProvisioningRepository =
- new FakeDeviceProvisioningRepository();
- deviceProvisioningRepository.setDeviceProvisioned(true);
+ mKosmos.getFakeDeviceProvisioningRepository().setDeviceProvisioned(true);
FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
@@ -294,7 +291,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase {
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- deviceProvisioningRepository,
+ mKosmos.getDeviceProvisioningInteractor(),
mDisableFlagsRepository,
mDozeParameters,
mKeyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 190ee81cc55a..460892af1154 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -49,7 +49,7 @@ import android.window.TransitionInfo;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 260bef8e98ce..5da3a569f91a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -25,6 +25,7 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import android.content.ComponentName;
import android.graphics.Rect;
@@ -32,11 +33,14 @@ import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.view.KeyEvent;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
+import android.view.accessibility.Flags;
import androidx.test.filters.SmallTest;
@@ -365,14 +369,50 @@ public class CommandQueueTest extends SysuiTestCase {
}
@Test
- public void testAddQsTile() {
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void addQsTile_withA11yQsShortcutFlagOff() {
ComponentName c = new ComponentName("testpkg", "testcls");
+
mCommandQueue.addQsTile(c);
waitForIdleSync();
+
verify(mCallbacks).addQsTile(eq(c));
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void addQsTileToFrontOrEnd_withA11yQsShortcutFlagOff_doNothing() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mCommandQueue.addQsTileToFrontOrEnd(c, true);
+ waitForIdleSync();
+
+ verifyZeroInteractions(mCallbacks);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void addQsTile_withA11yQsShortcutFlagOn() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mCommandQueue.addQsTile(c);
+ waitForIdleSync();
+
+ verify(mCallbacks).addQsTileToFrontOrEnd(eq(c), eq(false));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void addQsTileAtTheEnd_withA11yQsShortcutFlagOn() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mCommandQueue.addQsTileToFrontOrEnd(c, true);
+ waitForIdleSync();
+
+ verify(mCallbacks).addQsTileToFrontOrEnd(eq(c), eq(true));
+ }
+
+ @Test
public void testRemoveQsTile() {
ComponentName c = new ComponentName("testpkg", "testcls");
mCommandQueue.remQsTile(c);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 8cb064dd39a6..545053711179 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -56,8 +56,8 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.flow.emptyFlow
@@ -192,7 +192,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
shadeInteractor =
ShadeInteractorImpl(
testScope.backgroundScope,
- FakeDeviceProvisioningRepository(),
+ kosmos.deviceProvisioningInteractor,
FakeDisableFlagsRepository(),
mock(),
keyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 912c27d854fa..ea4ae17de5e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.StatusBarManager;
+import android.content.ComponentName;
import android.os.PowerManager;
import android.os.UserHandle;
import android.os.Vibrator;
@@ -190,4 +191,31 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
HapticFeedbackConstants.GESTURE_START
);
}
+
+ @Test
+ public void addQsTile_delegateCallToQsHost() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mSbcqCallbacks.addQsTile(c);
+
+ verify(mQSHost).addTile(c);
+ }
+
+ @Test
+ public void addQsTileToFrontOrEnd_toTheEnd_delegateCallToQsHost() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mSbcqCallbacks.addQsTileToFrontOrEnd(c, true);
+
+ verify(mQSHost).addTile(c, true);
+ }
+
+ @Test
+ public void addQsTileToFrontOrEnd_toTheFront_delegateCallToQsHost() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mSbcqCallbacks.addQsTileToFrontOrEnd(c, false);
+
+ verify(mQSHost).addTile(c, false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index 21c038ad476d..f53fc46d8ab2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -20,6 +20,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
@@ -36,6 +37,7 @@ import org.mockito.MockitoAnnotations
class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
private lateinit var underTest: DeviceBasedSatelliteViewModel
private lateinit var interactor: DeviceBasedSatelliteInteractor
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private val repo = FakeDeviceBasedSatelliteRepository()
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
@@ -45,6 +47,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ airplaneModeRepository = FakeAirplaneModeRepository()
interactor =
DeviceBasedSatelliteInteractor(
@@ -57,6 +60,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
DeviceBasedSatelliteViewModel(
interactor,
testScope.backgroundScope,
+ airplaneModeRepository,
)
}
@@ -72,6 +76,9 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
i1.isInService.value = false
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
// THEN icon is null because we should not be showing it
assertThat(latest).isNull()
}
@@ -88,11 +95,33 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
i1.isInService.value = true
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
// THEN icon is null because we have service
assertThat(latest).isNull()
}
@Test
+ fun icon_nullWhenShouldNotShow_apmIsEnabled() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+
+ // GIVEN apm is enabled
+ airplaneModeRepository.setIsAirplaneMode(true)
+
+ // THEN icon is null because we should not be showing it
+ assertThat(latest).isNull()
+ }
+
+ @Test
fun icon_satelliteIsOff() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index cd5d5ed0d08e..9d53e7d8c9b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy
+import android.app.ActivityOptions
import android.app.Notification
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
@@ -69,6 +70,8 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ setShareFullScreen()
+
controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
// Obtain useful MediaProjectionCallback
@@ -195,6 +198,14 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
}
@Test
+ fun isSensitiveStateActive_projectionActive_singleActivity_false() {
+ setShareSingleApp()
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
fun shouldProtectNotification_projectionInactive_false() {
val notificationEntry = mock(NotificationEntry::class.java)
@@ -202,30 +213,74 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
}
@Test
- fun shouldProtectNotification_projectionActive_fgsNotification_false() {
+ fun shouldProtectNotification_projectionActive_singleActivity_false() {
+ setShareSingleApp()
mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- val notificationEntry = mock(NotificationEntry::class.java)
- val sbn = mock(StatusBarNotification::class.java)
- val notification = mock(Notification::class.java)
- `when`(notificationEntry.sbn).thenReturn(sbn)
- `when`(sbn.notification).thenReturn(notification)
- `when`(notification.isFgsOrUij).thenReturn(true)
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_false() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupFgsNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
assertFalse(controller.shouldProtectNotification(notificationEntry))
}
@Test
+ fun shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_true() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupFgsNotificationEntry(TEST_PACKAGE_NAME)
+
+ assertTrue(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
+
+ assertTrue(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ private fun setShareFullScreen() {
+ `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+ `when`(mediaProjectionInfo.launchCookie).thenReturn(null)
+ }
+
+ private fun setShareSingleApp() {
+ `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+ `when`(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie())
+ }
+
+ private fun setupNotificationEntry(
+ packageName: String,
+ isFgs: Boolean = false
+ ): NotificationEntry {
val notificationEntry = mock(NotificationEntry::class.java)
val sbn = mock(StatusBarNotification::class.java)
val notification = mock(Notification::class.java)
`when`(notificationEntry.sbn).thenReturn(sbn)
+ `when`(sbn.packageName).thenReturn(packageName)
`when`(sbn.notification).thenReturn(notification)
- `when`(notification.isFgsOrUij).thenReturn(false)
+ `when`(notification.isFgsOrUij).thenReturn(isFgs)
- assertTrue(controller.shouldProtectNotification(notificationEntry))
+ return notificationEntry
+ }
+
+ private fun setupFgsNotificationEntry(packageName: String): NotificationEntry {
+ return setupNotificationEntry(packageName, /* isFgs= */ true)
+ }
+
+ companion object {
+ private const val TEST_PROJECTION_PACKAGE_NAME =
+ "com.android.systemui.statusbar.policy.projectionpackage"
+ private const val TEST_PACKAGE_NAME = "com.android.systemui.statusbar.policy.testpackage"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index abfff34f6dba..0669cb8b8ba5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.util.settings.FakeGlobalSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.launchIn
@@ -46,6 +47,7 @@ import org.mockito.Mockito.mock
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class UserRepositoryImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 7eba3f0602d5..9ea4142c9cff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -494,7 +494,7 @@ public class BubblesTest extends SysuiTestCase {
mShadeInteractor =
new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- deviceProvisioningRepository,
+ mKosmos.getDeviceProvisioningInteractor(),
new FakeDisableFlagsRepository(),
mDozeParameters,
keyguardRepository,
@@ -2098,6 +2098,26 @@ public class BubblesTest extends SysuiTestCase {
}
@Test
+ public void testShowOrHideAppBubble_addsFromOverflow() {
+ String appBubbleKey = Bubble.getAppBubbleKeyForApp(mAppBubbleIntent.getPackage(), mUser0);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+
+ // Collapse the stack so we don't need to wait for the dismiss animation in the test
+ mBubbleController.collapseStack();
+
+ // Dismiss the app bubble so it's in the overflow
+ mBubbleController.dismissBubble(appBubbleKey, Bubbles.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNotNull();
+
+ // Calling this while collapsed will re-add and expand the app bubble
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(appBubbleKey);
+ assertThat(mBubbleController.isStackExpanded()).isTrue();
+ assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
+ assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNull();
+ }
+
+ @Test
public void testCreateBubbleFromOngoingNotification() {
NotificationEntry notif = new NotificationEntryBuilder()
.setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
@@ -2217,7 +2237,8 @@ public class BubblesTest extends SysuiTestCase {
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
- mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), 500, 1000);
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(),
+ new Rect(500, 1000, 600, 1100));
assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index d91c5974815c..99dfe94af3df 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -21,11 +21,13 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter
import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.systemClock
@@ -36,8 +38,10 @@ val Kosmos.bouncerViewModel by Fixture {
applicationScope = testScope.backgroundScope,
mainDispatcher = testDispatcher,
bouncerInteractor = bouncerInteractor,
+ inputMethodInteractor = inputMethodInteractor,
simBouncerInteractor = simBouncerInteractor,
authenticationInteractor = authenticationInteractor,
+ selectedUserInteractor = selectedUserInteractor,
flags = sceneContainerFlags,
selectedUser = userSwitcherViewModel.selectedUser,
users = userSwitcherViewModel.users,
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 bc7e7af245a6..7301404eada0 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
@@ -36,6 +36,18 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
}
}
+ override fun deleteWidgetFromDb(widgetId: Int) {
+ if (_communalWidgets.value.none { it.appWidgetId == widgetId }) {
+ return
+ }
+
+ _communalWidgets.value = _communalWidgets.value.filter { it.appWidgetId != widgetId }
+ }
+
+ override fun deleteWidgetFromHost(widgetId: Int) {
+ deleteWidgetFromDb(widgetId)
+ }
+
private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
_communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority))
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index c818e9c8971c..c47f020a3b83 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -24,12 +24,15 @@ import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.mockito.mock
val Kosmos.communalInteractor by Fixture {
CommunalInteractor(
+ applicationScope = applicationCoroutineScope,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
mediaRepository = communalMediaRepository,
@@ -39,6 +42,8 @@ val Kosmos.communalInteractor by Fixture {
appWidgetHost = mock(),
keyguardInteractor = keyguardInteractor,
editWidgetsActivityStarter = editWidgetsActivityStarter,
+ logBuffer = logcatLogBuffer("CommunalInteractor"),
+ tableLogBuffer = mock(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
index adaea7cbf64d..9776b436555d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
@@ -21,6 +21,7 @@ import com.android.systemui.communal.data.repository.communalTutorialRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.util.mockito.mock
val Kosmos.communalTutorialInteractor by
Kosmos.Fixture {
@@ -30,5 +31,6 @@ val Kosmos.communalTutorialInteractor by
keyguardInteractor = keyguardInteractor,
communalRepository = communalRepository,
communalInteractor = communalInteractor,
+ tableLogBuffer = mock(),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
index 002862e949ba..a231212518ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.controls.panels
import android.os.UserHandle
+import com.android.systemui.kosmos.Kosmos
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -68,3 +69,6 @@ class FakeSelectedComponentRepository : SelectedComponentRepository {
}
}
}
+
+val Kosmos.selectedComponentRepository by
+ Kosmos.Fixture<FakeSelectedComponentRepository> { FakeSelectedComponentRepository() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt
index b6628db14235..b6628db14235 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DeviceStateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DeviceStateRepositoryKosmos.kt
new file mode 100644
index 000000000000..585506020618
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DeviceStateRepositoryKosmos.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.display.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.deviceStateRepository: DeviceStateRepository by
+ Kosmos.Fixture { fakeDeviceStateRepository }
+val Kosmos.fakeDeviceStateRepository: FakeDeviceStateRepository by
+ Kosmos.Fixture { FakeDeviceStateRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
new file mode 100644
index 000000000000..8e4461dd5b1e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.inputmethod.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.inputmethod.data.model.InputMethodModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.flowOf
+
+@SysUISingleton
+class FakeInputMethodRepository : InputMethodRepository {
+
+ private var usersToEnabledInputMethods: MutableMap<Int, Flow<InputMethodModel>> = mutableMapOf()
+
+ var selectedInputMethodSubtypes = listOf<InputMethodModel.Subtype>()
+
+ /**
+ * The display ID on which the input method picker dialog was shown, or `null` if the dialog was
+ * not shown.
+ */
+ var inputMethodPickerShownDisplayId: Int? = null
+
+ fun setEnabledInputMethods(userId: Int, vararg enabledInputMethods: InputMethodModel) {
+ usersToEnabledInputMethods[userId] = enabledInputMethods.asFlow()
+ }
+
+ override suspend fun enabledInputMethods(
+ userId: Int,
+ fetchSubtypes: Boolean,
+ ): Flow<InputMethodModel> {
+ return usersToEnabledInputMethods[userId] ?: flowOf()
+ }
+
+ override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> =
+ selectedInputMethodSubtypes
+
+ override suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean) {
+ inputMethodPickerShownDisplayId = displayId
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryKosmos.kt
new file mode 100644
index 000000000000..b71b9d878876
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryKosmos.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.inputmethod.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.inputMethodRepository: InputMethodRepository by
+ Kosmos.Fixture { fakeInputMethodRepository }
+val Kosmos.fakeInputMethodRepository by Kosmos.Fixture { FakeInputMethodRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorKosmos.kt
new file mode 100644
index 000000000000..da7757565888
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorKosmos.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.systemui.inputmethod.domain.interactor
+
+import com.android.systemui.inputmethod.data.repository.inputMethodRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.inputMethodInteractor by
+ Kosmos.Fixture {
+ InputMethodInteractor(
+ repository = inputMethodRepository,
+ )
+ }
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 083de107c971..b9a3d383fc13 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
@@ -43,6 +43,8 @@ import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.util.time.systemClock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -80,6 +82,8 @@ class KosmosJavaAdapter(
val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
val communalInteractor by lazy { kosmos.communalInteractor }
val sceneContainerPlugin by lazy { kosmos.sceneContainerPlugin }
+ val deviceProvisioningInteractor by lazy { kosmos.deviceProvisioningInteractor }
+ val fakeDeviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository }
init {
kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt
new file mode 100644
index 000000000000..9410ce6d657a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.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.qs.tiles.impl.fontscaling
+
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsFontScalingTileConfig by
+ Kosmos.Fixture { QSAccessibilityModule.provideFontScalingTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index afd37b3f92dc..2bd76bec6852 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -29,8 +29,8 @@ import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.dozeParameters
-import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.user.domain.interactor.userSwitcherInteractor
var Kosmos.baseShadeInteractor: BaseShadeInteractor by
@@ -63,7 +63,7 @@ val Kosmos.shadeInteractorImpl by
Kosmos.Fixture {
ShadeInteractorImpl(
scope = applicationCoroutineScope,
- deviceProvisioningRepository = deviceProvisioningRepository,
+ deviceProvisioningInteractor = deviceProvisioningInteractor,
disableFlagsRepository = disableFlagsRepository,
dozeParams = dozeParameters,
keyguardRepository = fakeKeyguardRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
index f2f3a5a1ad72..d79633ae72ba 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
@@ -25,5 +26,6 @@ val Kosmos.notificationStackAppearanceViewModel by Fixture {
NotificationStackAppearanceViewModel(
stackAppearanceInteractor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
+ sceneInteractor = sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 7c398cd45f90..549a77513e9c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -20,7 +20,9 @@ import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.occludedToLockscreenTransitionViewModel
@@ -40,6 +42,8 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
communalInteractor = communalInteractor,
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
+ dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
+ lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel,
aodBurnInViewModel = aodBurnInViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt
new file mode 100644
index 000000000000..84bd3e8ff08d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.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.systemui.statusbar.policy.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
+
+val Kosmos.deviceProvisioningInteractor by Fixture {
+ DeviceProvisioningInteractor(
+ repository = deviceProvisioningRepository,
+ )
+}
diff --git a/ravenwood/README-ravenwood+mockito.md b/ravenwood/README-ravenwood+mockito.md
deleted file mode 100644
index 6adb61441bb1..000000000000
--- a/ravenwood/README-ravenwood+mockito.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Ravenwood and Mockito
-
-Last update: 2023-11-13
-
-- As of 2023-11-13, `external/mockito` is based on version 2.x.
-- Mockito didn't support static mocking before 3.4.0.
- See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48
-
-- Latest Mockito is 5.*. According to https://github.com/mockito/mockito:
- `Mockito 3 does not introduce any breaking API changes, but now requires Java 8 over Java 6 for Mockito 2. Mockito 4 removes deprecated API. Mockito 5 switches the default mockmaker to mockito-inline, and now requires Java 11.`
-
-- Mockito now supports Android natively.
- See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.1
- - But it's unclear at this point to omakoto@ how the `mockito-android` module is built.
-
-- Potential plan:
- - Ideal option:
- - If we can update `external/mockito`, that'd be great, but it may not work because
- Mockito has removed the deprecated APIs.
- - Second option:
- - Import the latest mockito as `external/mockito-new`, and require ravenwood
- to use this one.
- - The latest mockito needs be exposed to all of 1) device tests, 2) host tests, and 3) ravenwood tests.
- - This probably will require the latest `bytebuddy` and `objenesis`. \ No newline at end of file
diff --git a/ravenwood/bulk_enable.py b/ravenwood/bulk_enable.py
new file mode 100644
index 000000000000..36d398cc160c
--- /dev/null
+++ b/ravenwood/bulk_enable.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+"""
+Tool to bulk-enable tests that are now passing on Ravenwood.
+
+Currently only offers to include classes which are fully passing; ignores
+classes that have partial success.
+
+Typical usage:
+$ ENABLE_PROBE_IGNORED=1 atest MyTestsRavenwood
+$ cd /path/to/tests/root
+$ python bulk_enable.py /path/to/atest/output/host_log.txt
+"""
+
+import collections
+import os
+import re
+import subprocess
+import sys
+
+re_result = re.compile("I/ModuleListener.+?null-device-0 (.+?)#(.+?) ([A-Z_]+)(.*)$")
+
+ANNOTATION = "@android.platform.test.annotations.EnabledOnRavenwood"
+SED_ARG = "s/^((public )?class )/%s\\n\\1/g" % (ANNOTATION)
+
+STATE_PASSED = "PASSED"
+STATE_FAILURE = "FAILURE"
+STATE_ASSUMPTION_FAILURE = "ASSUMPTION_FAILURE"
+STATE_CANDIDATE = "CANDIDATE"
+
+stats_total = collections.defaultdict(int)
+stats_class = collections.defaultdict(lambda: collections.defaultdict(int))
+stats_method = collections.defaultdict()
+
+with open(sys.argv[1]) as f:
+ for line in f.readlines():
+ result = re_result.search(line)
+ if result:
+ clazz, method, state, msg = result.groups()
+ if state == STATE_FAILURE and "actually passed under Ravenwood" in msg:
+ state = STATE_CANDIDATE
+ stats_total[state] += 1
+ stats_class[clazz][state] += 1
+ stats_method[(clazz, method)] = state
+
+# Find classes who are fully "candidates" (would be entirely green if enabled)
+num_enabled = 0
+for clazz in stats_class.keys():
+ stats = stats_class[clazz]
+ if STATE_CANDIDATE in stats and len(stats) == 1:
+ num_enabled += stats[STATE_CANDIDATE]
+ print("Enabling fully-passing class", clazz)
+ clazz_match = re.compile("%s\.(kt|java)" % (clazz.split(".")[-1]))
+ for root, dirs, files in os.walk("."):
+ for f in files:
+ if clazz_match.match(f):
+ path = os.path.join(root, f)
+ subprocess.run(["sed", "-i", "-E", SED_ARG, path])
+
+print("Overall stats", stats_total)
+print("Candidates actually enabled", num_enabled)
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index a797b1d61b2a..3670459c0300 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -20,9 +20,14 @@ import android.app.Instrumentation;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
+import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.os.RuntimeInit;
+
+import org.junit.runner.Description;
+
import java.io.PrintStream;
import java.util.Map;
import java.util.concurrent.Executors;
@@ -50,6 +55,8 @@ public class RavenwoodRuleImpl {
}
public static void init(RavenwoodRule rule) {
+ RuntimeInit.redirectLogStreams();
+
android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
android.os.Binder.init$ravenwood();
android.os.SystemProperties.init$ravenwood(
@@ -92,6 +99,13 @@ public class RavenwoodRuleImpl {
android.os.Process.reset$ravenwood();
}
+ public static void logTestRunner(String label, Description description) {
+ // This message string carefully matches the exact format emitted by on-device tests, to
+ // aid developers in debugging raw text logs
+ Log.e("TestRunner", label + ": " + description.getMethodName()
+ + "(" + description.getTestClass().getName() + ")");
+ }
+
private static void dumpStacks() {
final PrintStream out = System.err;
out.println("-----BEGIN ALL THREAD STACKS-----");
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 1e7cbf6d802d..0285b386ed13 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -280,9 +280,14 @@ public class RavenwoodRule implements TestRule {
public void evaluate() throws Throwable {
Assume.assumeTrue(shouldEnableOnRavenwood(description));
+ RavenwoodRuleImpl.logTestRunner("started", description);
RavenwoodRuleImpl.init(RavenwoodRule.this);
try {
base.evaluate();
+ RavenwoodRuleImpl.logTestRunner("finished", description);
+ } catch (Throwable t) {
+ RavenwoodRuleImpl.logTestRunner("failed", description);
+ throw t;
} finally {
RavenwoodRuleImpl.reset(RavenwoodRule.this);
}
@@ -300,6 +305,8 @@ public class RavenwoodRule implements TestRule {
@Override
public void evaluate() throws Throwable {
Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
+
+ RavenwoodRuleImpl.logTestRunner("started", description);
RavenwoodRuleImpl.init(RavenwoodRule.this);
try {
base.evaluate();
@@ -309,6 +316,7 @@ public class RavenwoodRule implements TestRule {
Assume.assumeTrue(shouldEnableOnRavenwood(description));
throw t;
} finally {
+ RavenwoodRuleImpl.logTestRunner("finished", description);
RavenwoodRuleImpl.reset(RavenwoodRule.this);
}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index d0c2e18be8df..7d172f2a83c0 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -16,6 +16,8 @@
package android.platform.test.ravenwood;
+import org.junit.runner.Description;
+
public class RavenwoodRuleImpl {
public static boolean isOnRavenwood() {
return false;
@@ -28,4 +30,8 @@ public class RavenwoodRuleImpl {
public static void reset(RavenwoodRule rule) {
// No-op when running on a real device
}
+
+ public static void logTestRunner(String label, Description description) {
+ // No-op when running on a real device
+ }
}
diff --git a/ravenwood/list-ravenwood-tests.sh b/ravenwood/list-ravenwood-tests.sh
new file mode 100755
index 000000000000..fb9b823ee93b
--- /dev/null
+++ b/ravenwood/list-ravenwood-tests.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+# 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.
+
+# List all the ravenwood test modules.
+
+jq -r 'to_entries[] | select( .value.compatibility_suites | index("ravenwood-tests") ) | .key' "$OUT/module-info.json"
diff --git a/ravenwood/mockito/Android.bp b/ravenwood/mockito/Android.bp
index 4135022d2bc9..a74bca47f692 100644
--- a/ravenwood/mockito/Android.bp
+++ b/ravenwood/mockito/Android.bp
@@ -7,16 +7,6 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-// Ravenwood tests run on the hostside, so we need mockit of the host variant.
-// But we need to use it in modules of the android variant, so we "wash" the variant with it.
-java_host_for_device {
- name: "mockito_ravenwood",
- libs: [
- "mockito",
- "objenesis",
- ],
-}
-
android_ravenwood_test {
name: "RavenwoodMockitoTest",
@@ -26,8 +16,6 @@ android_ravenwood_test {
static_libs: [
"junit",
"truth",
-
- "mockito_ravenwood",
],
libs: [
"android.test.mock",
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
index 364a86a0ec5f..1284d64b9a90 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
@@ -22,6 +22,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
+import android.os.Parcel;
import android.platform.test.ravenwood.RavenwoodRule;
import org.junit.Rule;
@@ -78,4 +79,13 @@ public class RavenwoodMockitoTest {
assertThat(object.getPackageName()).isEqualTo("android");
}
+
+ @Test
+ public void testMockFinalClass() {
+ var object = mock(Parcel.class);
+
+ when(object.readInt()).thenReturn(123);
+
+ assertThat(object.readInt()).isEqualTo(123);
+ }
}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index b775f9ad47ad..e49b64ebdbef 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -5,6 +5,7 @@ com.android.internal.util.ArrayUtils
com.android.internal.logging.MetricsLogger
com.android.internal.logging.testing.FakeMetricsLogger
com.android.internal.logging.testing.UiEventLoggerFake
+com.android.internal.os.AndroidPrintStream
com.android.internal.os.BatteryStatsHistory
com.android.internal.os.BatteryStatsHistory$TraceDelegate
com.android.internal.os.BatteryStatsHistory$VarintParceler
@@ -16,6 +17,7 @@ com.android.internal.os.MonotonicClock
com.android.internal.os.PowerProfile
com.android.internal.os.PowerStats
com.android.internal.os.PowerStats$Descriptor
+com.android.internal.os.RuntimeInit
com.android.internal.power.ModemPowerProfile
android.util.AtomicFile
diff --git a/ravenwood/run-ravenwood-tests.sh b/ravenwood/run-ravenwood-tests.sh
new file mode 100755
index 000000000000..3f4b8a79e864
--- /dev/null
+++ b/ravenwood/run-ravenwood-tests.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# 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.
+
+# Run all the ravenwood tests.
+
+# "echo" is to remove the newlines
+all_tests=$(echo $(${0%/*}/list-ravenwood-tests.sh) )
+
+echo "Running tests: $all_tests"
+atest $all_tests
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 63784ba61150..44144f84f4e9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -5725,10 +5725,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
@Override
- public void attachAccessibilityOverlayToDisplay_enforcePermission(
+ public void attachAccessibilityOverlayToDisplay(
int displayId, SurfaceControl sc) {
mContext.enforceCallingPermission(
- INTERNAL_SYSTEM_WINDOW, "attachAccessibilityOverlayToDisplay_enforcePermission");
+ INTERNAL_SYSTEM_WINDOW, "attachAccessibilityOverlayToDisplay");
mMainHandler.sendMessage(
obtainMessage(
AccessibilityManagerService::attachAccessibilityOverlayToDisplayInternal,
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 727721d5319f..532db126bff2 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -12,4 +12,11 @@ flag {
namespace: "autofill"
description: "Guards against Autofill-Credman integration phase 2"
bug: "320730001"
-} \ No newline at end of file
+}
+
+flag {
+ name: "autofill_credman_dev_integration"
+ namespace: "autofill"
+ description: "Guards against Autofill-Credman Phase1 developer integration via new APIs"
+ bug: "320730001"
+}
diff --git a/services/backup/Android.bp b/services/backup/Android.bp
index d08a97e2a573..2a85eb66f6c5 100644
--- a/services/backup/Android.bp
+++ b/services/backup/Android.bp
@@ -21,7 +21,6 @@ java_library_static {
libs: ["services.core"],
static_libs: [
"app-compat-annotations",
- "backup_flags_lib",
],
lint: {
baseline_filename: "lint-baseline.xml",
@@ -33,8 +32,3 @@ aconfig_declarations {
package: "com.android.server.backup",
srcs: ["flags.aconfig"],
}
-
-java_aconfig_library {
- name: "backup_flags_lib",
- aconfig_declarations: "backup_flags",
-}
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 1416c888f790..6a63b3a9db24 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -24,4 +24,12 @@ flag {
description: "Enables the write buffer to pipes to be of maximum size."
bug: "265976737"
is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_clear_pipe_after_restore_file"
+ namespace: "onboarding"
+ description: "Enables clearing the pipe buffer after restoring a single file to a BackupAgent."
+ bug: "320633449"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 055970819e28..d85dd879e21d 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -732,6 +732,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
monitoringExtras);
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ mStatus = BackupTransport.TRANSPORT_ERROR;
nextState = UnifiedRestoreState.FINAL;
} finally {
executeNextState(nextState);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 8e35b7455d02..89896c3735b6 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -211,7 +211,6 @@ java_library_static {
"com_android_wm_shell_flags_lib",
"com.android.server.utils_aconfig-java",
"service-jobscheduler-deviceidle.flags-aconfig-java",
- "backup_flags_lib",
"policy_flags_lib",
"net_flags_lib",
"stats_flags_lib",
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index a1e6b58f971f..81c9ee790d72 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -81,7 +81,7 @@ public abstract class IntentResolver<F, R extends Object> {
* Returns whether an intent matches the IntentFilter with a pre-resolved type.
*/
public static boolean intentMatchesFilter(
- IntentFilter filter, Intent intent, String resolvedType, boolean defaultOnly) {
+ IntentFilter filter, Intent intent, String resolvedType) {
final boolean debug = localLOGV
|| ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
@@ -97,10 +97,6 @@ public abstract class IntentResolver<F, R extends Object> {
int match = filter.match(intent.getAction(), resolvedType, intent.getScheme(),
intent.getData(), intent.getCategories(), TAG);
- if (match >= 0 && defaultOnly && !filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
- match = IntentFilter.NO_MATCH_CATEGORY;
- }
-
if (match >= 0) {
if (debug) {
Slog.v(TAG, "Filter matched! match=0x" + Integer.toHexString(match));
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 374a17a3a23f..a5531ae65bbe 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2101,7 +2101,8 @@ public class ActivityManagerService extends IActivityManager.Stub
// Start watching app ops after we and the package manager are up and running.
mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
new IAppOpsCallback.Stub() {
- @Override public void opChanged(int op, int uid, String packageName) {
+ @Override public void opChanged(int op, int uid, String packageName,
+ String persistentDeviceId) {
if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) {
if (getAppOpsManager().checkOpNoThrow(op, uid, packageName)
!= AppOpsManager.MODE_ALLOWED) {
@@ -2115,7 +2116,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mAppOpsService.startWatchingActive(cameraOp, new IAppOpsActiveCallback.Stub() {
@Override
public void opActiveChanged(int op, int uid, String packageName, String attributionTag,
- boolean active, @AttributionFlags int attributionFlags,
+ int virtualDeviceId, boolean active, @AttributionFlags int attributionFlags,
int attributionChainId) {
cameraActiveChanged(uid, active);
}
diff --git a/services/core/java/com/android/server/am/AppPermissionTracker.java b/services/core/java/com/android/server/am/AppPermissionTracker.java
index 18a91535a34c..c641b35475e9 100644
--- a/services/core/java/com/android/server/am/AppPermissionTracker.java
+++ b/services/core/java/com/android/server/am/AppPermissionTracker.java
@@ -393,7 +393,7 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy
private class MyAppOpsCallback extends IAppOpsCallback.Stub {
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName, String persistentDeviceId) {
mHandler.obtainMessage(MyHandler.MSG_APPOPS_CHANGED, op, uid, packageName)
.sendToTarget();
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 7aafda59298c..1207616dcb8d 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -162,6 +162,7 @@ public class SettingsToPropertiesMapper {
"nfc",
"pdf_viewer",
"pixel_audio_android",
+ "pixel_biometrics_face",
"pixel_bluetooth",
"pixel_connectivity_gps",
"pixel_system_sw_video",
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index 091696737dcf..9535db8571f2 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -220,7 +220,7 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface
}
for (int i = 0; i < listenersCopy.size(); i++) {
- listenersCopy.get(i).onUidModeChanged(uid, op, mode);
+ listenersCopy.get(i).onUidModeChanged(uid, op, mode, persistentDeviceId);
}
return true;
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
index f056f6b10b0d..8b4ea67547cb 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -176,8 +176,9 @@ public interface AppOpsCheckingServiceInterface {
* @param uid The UID whose appop mode was changed.
* @param code The op code that was changed.
* @param mode The new mode.
+ * @param persistentDeviceId the device whose mode was changed
*/
- void onUidModeChanged(int uid, int code, int mode);
+ void onUidModeChanged(int uid, int code, int mode, String persistentDeviceId);
/**
* Invoked when a package's appop mode is changed.
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 2ed217a89397..5c95d433a6c1 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -166,6 +166,7 @@ import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemServiceManager;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.pm.PackageList;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.UserManagerInternal;
@@ -275,6 +276,10 @@ public class AppOpsService extends IAppOpsService.Stub {
= new AppOpsManagerInternalImpl();
@Nullable private final DevicePolicyManagerInternal dpmi =
LocalServices.getService(DevicePolicyManagerInternal.class);
+ @Nullable private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
+
+ /** Map of virtual device id -> persistent device id. */
+ private final SparseArray<String> mKnownDeviceIds = new SparseArray<>();
private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
@@ -595,45 +600,71 @@ public class AppOpsService extends IAppOpsService.Stub {
final UidState uidState;
final @NonNull String packageName;
- /** attributionTag -> AttributedOp */
- final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
+ /**
+ * Map to retrieve {@link AttributedOp} for a particular device and attribution tag.
+ *
+ * ArrayMap<Persistent Device Id, ArrayMap<Attribution Tag, AttributedOp>>
+ */
+ final ArrayMap<String, ArrayMap<String, AttributedOp>> mDeviceAttributedOps =
+ new ArrayMap<String, ArrayMap<String, AttributedOp>>(1);
Op(UidState uidState, String packageName, int op, int uid) {
this.op = op;
this.uid = uid;
this.uidState = uidState;
this.packageName = packageName;
+ // We keep an invariant that the persistent device will always have an entry in
+ // mDeviceAttributedOps.
+ mDeviceAttributedOps.put(PERSISTENT_DEVICE_ID_DEFAULT,
+ new ArrayMap<String, AttributedOp>());
}
void removeAttributionsWithNoTime() {
- for (int i = mAttributions.size() - 1; i >= 0; i--) {
- if (!mAttributions.valueAt(i).hasAnyTime()) {
- mAttributions.removeAt(i);
+ for (int deviceIndex = mDeviceAttributedOps.size() - 1; deviceIndex >= 0;
+ deviceIndex--) {
+ ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.valueAt(
+ deviceIndex);
+ for (int tagIndex = attributedOps.size() - 1; tagIndex >= 0; tagIndex--) {
+ if (!attributedOps.valueAt(tagIndex).hasAnyTime()) {
+ attributedOps.removeAt(tagIndex);
+ }
+ }
+ if (!Objects.equals(PERSISTENT_DEVICE_ID_DEFAULT,
+ mDeviceAttributedOps.keyAt(deviceIndex)) && attributedOps.isEmpty()) {
+ mDeviceAttributedOps.removeAt(deviceIndex);
}
}
}
private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
- @Nullable String attributionTag) {
- AttributedOp attributedOp;
+ @Nullable String attributionTag, String persistentDeviceId) {
+ ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
+ persistentDeviceId);
+ if (attributedOps == null) {
+ attributedOps = new ArrayMap<>();
+ mDeviceAttributedOps.put(persistentDeviceId, attributedOps);
+ }
+ AttributedOp attributedOp = attributedOps.get(attributionTag);
- attributedOp = mAttributions.get(attributionTag);
if (attributedOp == null) {
- attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent);
- mAttributions.put(attributionTag, attributedOp);
+ attributedOp = new AttributedOp(AppOpsService.this, attributionTag,
+ persistentDeviceId, parent);
+ attributedOps.put(attributionTag, attributedOp);
}
return attributedOp;
}
@NonNull OpEntry createEntryLocked() {
- final int numAttributions = mAttributions.size();
-
+ // TODO(b/308201969): Update this method when we introduce disk persistence of events
+ // for accesses on external devices.
+ final ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
+ PERSISTENT_DEVICE_ID_DEFAULT);
final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
- new ArrayMap<>(numAttributions);
- for (int i = 0; i < numAttributions; i++) {
- attributionEntries.put(mAttributions.keyAt(i),
- mAttributions.valueAt(i).createAttributedOpEntryLocked());
+ new ArrayMap<>(attributedOps.size());
+ for (int i = 0; i < attributedOps.size(); i++) {
+ attributionEntries.put(attributedOps.keyAt(i),
+ attributedOps.valueAt(i).createAttributedOpEntryLocked());
}
return new OpEntry(
@@ -644,17 +675,15 @@ public class AppOpsService extends IAppOpsService.Stub {
}
@NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
- final int numAttributions = mAttributions.size();
-
+ // TODO(b/308201969): Update this method when we introduce disk persistence of events
+ // for accesses on external devices.
+ final ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
+ PERSISTENT_DEVICE_ID_DEFAULT);
final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
- for (int i = 0; i < numAttributions; i++) {
- if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
- attributionEntries.put(mAttributions.keyAt(i),
- mAttributions.valueAt(i).createAttributedOpEntryLocked());
- break;
- }
+ if (attributedOps.get(attributionTag) != null) {
+ attributionEntries.put(attributionTag,
+ attributedOps.get(attributionTag).createAttributedOpEntryLocked());
}
-
return new OpEntry(
op,
mAppOpsCheckingService.getPackageMode(
@@ -663,13 +692,15 @@ public class AppOpsService extends IAppOpsService.Stub {
}
boolean isRunning() {
- final int numAttributions = mAttributions.size();
- for (int i = 0; i < numAttributions; i++) {
- if (mAttributions.valueAt(i).isRunning()) {
- return true;
+ for (int deviceIndex = 0; deviceIndex < mDeviceAttributedOps.size(); deviceIndex++) {
+ ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.valueAt(
+ deviceIndex);
+ for (int tagIndex = 0; tagIndex < attributedOps.size(); tagIndex++) {
+ if (attributedOps.valueAt(tagIndex).isRunning()) {
+ return true;
+ }
}
}
-
return false;
}
}
@@ -738,7 +769,15 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
- mCallback.opChanged(op, uid, packageName);
+ throw new IllegalStateException(
+ "unimplemented onOpModeChanged method called for op: " + op + " uid: " + uid
+ + " packageName: " + packageName);
+ }
+
+ @Override
+ public void onOpModeChanged(int op, int uid, String packageName, String persistentDeviceId)
+ throws RemoteException {
+ mCallback.opChanged(op, uid, packageName, persistentDeviceId);
}
}
@@ -910,6 +949,7 @@ public class AppOpsService extends IAppOpsService.Stub {
public AppOpsService(File recentAccessesFile, File storageFile, Handler handler,
Context context) {
mContext = context;
+ mKnownDeviceIds.put(Context.DEVICE_ID_DEFAULT, PERSISTENT_DEVICE_ID_DEFAULT);
for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
int switchCode = AppOpsManager.opToSwitch(switchedCode);
@@ -927,10 +967,11 @@ public class AppOpsService extends IAppOpsService.Stub {
mAppOpsCheckingService.addAppOpsModeChangedListener(
new AppOpsCheckingServiceInterface.AppOpsModeChangedListener() {
@Override
- public void onUidModeChanged(int uid, int code, int mode) {
+ public void onUidModeChanged(int uid, int code, int mode,
+ String persistentDeviceId) {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChangedForAllPkgsInUid, AppOpsService.this,
- code, uid, false));
+ code, uid, false, persistentDeviceId));
}
@Override
@@ -941,8 +982,9 @@ public class AppOpsService extends IAppOpsService.Stub {
packageName, code, mode, userId));
}
});
+ // Only notify default device as other devices are unaffected by restriction changes.
mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
- code -> notifyWatchersOfChange(code, UID_ANY));
+ code -> notifyWatchersOnDefaultDevice(code, UID_ANY));
LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
mStorageFile = new AtomicFile(storageFile, "appops_legacy");
@@ -1043,25 +1085,28 @@ public class AppOpsService extends IAppOpsService.Stub {
int numOps = ops.size();
for (int opNum = 0; opNum < numOps; opNum++) {
Op op = ops.valueAt(opNum);
+ for (int deviceIndex = op.mDeviceAttributedOps.size() - 1; deviceIndex >= 0;
+ deviceIndex--) {
+ ArrayMap<String, AttributedOp> attributedOps =
+ op.mDeviceAttributedOps.valueAt(deviceIndex);
+ for (int tagIndex = attributedOps.size() - 1; tagIndex >= 0;
+ tagIndex--) {
+ String tag = attributedOps.keyAt(tagIndex);
+ if (attributionTags.contains(tag)) {
+ // attribution still exist after upgrade
+ continue;
+ }
- int numAttributions = op.mAttributions.size();
- for (int attributionNum = numAttributions - 1; attributionNum >= 0;
- attributionNum--) {
- String attributionTag = op.mAttributions.keyAt(attributionNum);
-
- if (attributionTags.contains(attributionTag)) {
- // attribution still exist after upgrade
- continue;
- }
-
- String newAttributionTag = dstAttributionTags.get(attributionTag);
+ String newAttributionTag = dstAttributionTags.get(tag);
- AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
- newAttributionTag);
- newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
- op.mAttributions.removeAt(attributionNum);
+ AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
+ newAttributionTag,
+ op.mDeviceAttributedOps.keyAt(deviceIndex));
+ newAttributedOp.add(attributedOps.get(tag));
+ attributedOps.remove(tag);
- scheduleFastWriteLocked();
+ scheduleFastWriteLocked();
+ }
}
}
}
@@ -1070,6 +1115,8 @@ public class AppOpsService extends IAppOpsService.Stub {
};
public void systemReady() {
+ mVirtualDeviceManagerInternal = LocalServices.getService(
+ VirtualDeviceManagerInternal.class);
mAppOpsCheckingService.systemReady();
initializeUidStates();
@@ -1144,7 +1191,17 @@ public class AppOpsService extends IAppOpsService.Stub {
final String changedPkg = changedPkgs[i];
// We trust packagemanager to insert matching uid and packageNames in the
// extras
- notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
+ Set<String> devices;
+ if (mVirtualDeviceManagerInternal != null) {
+ devices = mVirtualDeviceManagerInternal.getAllPersistentDeviceIds();
+ } else {
+ devices = new ArraySet<>();
+ devices.add(PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+ for (String device: devices) {
+ notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg,
+ device);
+ }
}
}
}
@@ -1295,17 +1352,19 @@ public class AppOpsService extends IAppOpsService.Stub {
final int numOps = removedOps.size();
for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = removedOps.valueAt(opNum);
-
- final int numAttributions = op.mAttributions.size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
-
- while (attributedOp.isRunning()) {
- attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
- }
- while (attributedOp.isPaused()) {
- attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
+ for (int deviceIndex = 0; deviceIndex < op.mDeviceAttributedOps.size();
+ deviceIndex++) {
+ ArrayMap<String, AttributedOp> attributedOps =
+ op.mDeviceAttributedOps.valueAt(deviceIndex);
+ for (int tagIndex = 0; tagIndex < attributedOps.size(); tagIndex++) {
+ AttributedOp attributedOp = attributedOps.valueAt(tagIndex);
+
+ while (attributedOp.isRunning()) {
+ attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
+ }
+ while (attributedOp.isPaused()) {
+ attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
+ }
}
}
}
@@ -1380,7 +1439,7 @@ public class AppOpsService extends IAppOpsService.Stub {
== AppOpsManager.MODE_FOREGROUND) {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChangedForAllPkgsInUid,
- this, code, uidState.uid, true));
+ this, code, uidState.uid, true, PERSISTENT_DEVICE_ID_DEFAULT));
} else if (!uidState.pkgOps.isEmpty()) {
final ArraySet<OnOpModeChangedListener> listenerSet =
mOpModeWatchers.get(code);
@@ -1405,7 +1464,8 @@ public class AppOpsService extends IAppOpsService.Stub {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChanged,
this, listenerSet.valueAt(cbi), code, uidState.uid,
- uidState.pkgOps.keyAt(pkgi)));
+ uidState.pkgOps.keyAt(pkgi),
+ PERSISTENT_DEVICE_ID_DEFAULT));
}
}
}
@@ -1422,14 +1482,15 @@ public class AppOpsService extends IAppOpsService.Stub {
int numOps = ops.size();
for (int opNum = 0; opNum < numOps; opNum++) {
Op op = ops.valueAt(opNum);
-
- int numAttributions = op.mAttributions.size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- AttributedOp attributedOp = op.mAttributions.valueAt(
- attributionNum);
-
- attributedOp.onUidStateChanged(state);
+ for (int deviceIndex = 0; deviceIndex < op.mDeviceAttributedOps.size();
+ deviceIndex++) {
+ ArrayMap<String, AttributedOp> attributedOps =
+ op.mDeviceAttributedOps.valueAt(deviceIndex);
+ for (int tagIndex = 0; tagIndex < attributedOps.size();
+ tagIndex++) {
+ AttributedOp attributedOp = attributedOps.valueAt(tagIndex);
+ attributedOp.onUidStateChanged(state);
+ }
}
}
}
@@ -1774,7 +1835,7 @@ public class AppOpsService extends IAppOpsService.Stub {
private void pruneOpLocked(Op op, int uid, String packageName) {
op.removeAttributionsWithNoTime();
- if (op.mAttributions.isEmpty()) {
+ if (op.mDeviceAttributedOps.isEmpty()) {
Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
if (ops != null) {
ops.remove(op.op);
@@ -1870,8 +1931,10 @@ public class AppOpsService extends IAppOpsService.Stub {
uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code, mode)) {
return;
}
+ // TODO(b/266164193): Ensure this behavior is device-aware after uid op mode for runtime
+ // permissions is deprecated.
if (mode != MODE_ERRORED && mode != previousMode) {
- updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+ updateStartedOpModeForUidForDefaultDeviceLocked(code, mode == MODE_IGNORED, uid);
}
}
@@ -1884,8 +1947,10 @@ public class AppOpsService extends IAppOpsService.Stub {
* @param code The op that changed
* @param uid The uid the op was changed for
* @param onlyForeground Only notify watchers that watch for foreground changes
+ * @param persistentDeviceId device the op was changed for
*/
- private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground) {
+ private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
+ String persistentDeviceId) {
String[] uidPackageNames = getPackagesForUid(uid);
ArrayMap<OnOpModeChangedListener, ArraySet<String>> callbackSpecs = null;
synchronized (this) {
@@ -1951,17 +2016,17 @@ public class AppOpsService extends IAppOpsService.Stub {
final OnOpModeChangedListener callback = callbackSpecs.keyAt(i);
final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
if (reportedPackageNames == null) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, callback, code, uid, (String) null));
+ mHandler.sendMessage(
+ PooledLambda.obtainMessage(AppOpsService::notifyOpChanged, this,
+ callback, code, uid, (String) null, persistentDeviceId));
} else {
final int reportedPackageCount = reportedPackageNames.size();
for (int j = 0; j < reportedPackageCount; j++) {
final String reportedPackageName = reportedPackageNames.valueAt(j);
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, callback, code, uid, reportedPackageName));
+ mHandler.sendMessage(
+ PooledLambda.obtainMessage(AppOpsService::notifyOpChanged, this,
+ callback, code, uid, reportedPackageName, persistentDeviceId));
}
}
}
@@ -1999,13 +2064,17 @@ public class AppOpsService extends IAppOpsService.Stub {
}
scheduleFastWriteLocked();
if (mode != MODE_ERRORED) {
- updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+ // Notify on PERSISTENT_DEVICE_ID_DEFAULT only as only uid modes are device-aware,
+ // not package modes.
+ updateStartedOpModeForUidForDefaultDeviceLocked(code, mode == MODE_IGNORED, uid);
}
}
if (repCbs != null && uid != -1) {
+ // Notify on PERSISTENT_DEVICE_ID_DEFAULT only as only uid modes are device-aware, not
+ // package modes.
mHandler.sendMessage(PooledLambda.obtainMessage(AppOpsService::notifyOpChanged, this,
- repCbs, code, uid, packageName));
+ repCbs, code, uid, packageName, PERSISTENT_DEVICE_ID_DEFAULT));
}
}
@@ -2161,15 +2230,15 @@ public class AppOpsService extends IAppOpsService.Stub {
}
private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
- int uid, String packageName) {
+ int uid, String packageName, String persistentDeviceId) {
for (int i = 0; i < callbacks.size(); i++) {
final OnOpModeChangedListener callback = callbacks.valueAt(i);
- notifyOpChanged(callback, code, uid, packageName);
+ notifyOpChanged(callback, code, uid, packageName, persistentDeviceId);
}
}
private void notifyOpChanged(OnOpModeChangedListener onModeChangedListener, int code,
- int uid, String packageName) {
+ int uid, String packageName, String persistentDeviceId) {
Objects.requireNonNull(onModeChangedListener);
if (uid != UID_ANY && onModeChangedListener.getWatchingUid() >= 0
@@ -2197,7 +2266,8 @@ public class AppOpsService extends IAppOpsService.Stub {
onModeChangedListener.getCallingUid())) {
continue;
}
- onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName);
+ onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName,
+ persistentDeviceId);
} catch (RemoteException e) {
/* ignore */
} finally {
@@ -2289,7 +2359,8 @@ public class AppOpsService extends IAppOpsService.Stub {
boolean changed = false;
for (int i = mUidStates.size() - 1; i >= 0; i--) {
UidState uidState = mUidStates.valueAt(i);
- // TODO(b/299330771): Check non default modes for all devices.
+ // TODO(b/266164193): Ensure this behavior is device-aware after uid op mode for
+ // runtime permissions is deprecated.
SparseIntArray opModes =
mAppOpsCheckingService.getNonDefaultUidModes(
uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT);
@@ -2301,7 +2372,6 @@ public class AppOpsService extends IAppOpsService.Stub {
int previousMode = opModes.valueAt(j);
int newMode = isUidOpGrantedByRole(uidState.uid, code) ? MODE_ALLOWED :
AppOpsManager.opToDefaultMode(code);
- // TODO(b/299330771): Set mode for all necessary devices.
mAppOpsCheckingService.setUidMode(
uidState.uid,
PERSISTENT_DEVICE_ID_DEFAULT,
@@ -2375,7 +2445,7 @@ public class AppOpsService extends IAppOpsService.Stub {
allChanges = addChange(allChanges, curOp.op, uid, packageName,
previousMode);
curOp.removeAttributionsWithNoTime();
- if (curOp.mAttributions.isEmpty()) {
+ if (curOp.mDeviceAttributedOps.isEmpty()) {
pkgOps.removeAt(j);
}
}
@@ -2399,9 +2469,18 @@ public class AppOpsService extends IAppOpsService.Stub {
ArrayList<ChangeRec> reports = ent.getValue();
for (int i=0; i<reports.size(); i++) {
ChangeRec rep = reports.get(i);
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, cb, rep.op, rep.uid, rep.pkg));
+ Set<String> devices;
+ if (mVirtualDeviceManagerInternal != null) {
+ devices = mVirtualDeviceManagerInternal.getAllPersistentDeviceIds();
+ } else {
+ devices = new ArraySet<>();
+ devices.add(PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+ for (String device: devices) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsService::notifyOpChanged,
+ this, cb, rep.op, rep.uid, rep.pkg, device));
+ }
}
}
}
@@ -2595,6 +2674,12 @@ public class AppOpsService extends IAppOpsService.Stub {
private int checkOperationImpl(int code, int uid, String packageName,
@Nullable String attributionTag, int virtualDeviceId, boolean raw) {
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG,
+ "checkOperationImpl returned MODE_IGNORED as virtualDeviceId " + virtualDeviceId
+ + " is invalid");
+ return AppOpsManager.MODE_IGNORED;
+ }
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return AppOpsManager.opToDefaultMode(code);
}
@@ -2603,7 +2688,8 @@ public class AppOpsService extends IAppOpsService.Stub {
if (resolvedPackageName == null) {
return AppOpsManager.MODE_IGNORED;
}
- return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
+ return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
+ virtualDeviceId, raw);
}
/**
@@ -2617,7 +2703,7 @@ public class AppOpsService extends IAppOpsService.Stub {
* @return The mode of the op
*/
private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, boolean raw) {
+ @Nullable String attributionTag, int virtualDeviceId, boolean raw) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, null);
@@ -2630,19 +2716,19 @@ public class AppOpsService extends IAppOpsService.Stub {
return AppOpsManager.MODE_IGNORED;
}
synchronized (this) {
- if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
+ if (isOpRestrictedLocked(uid, code, packageName, attributionTag, virtualDeviceId,
+ pvr.bypass, true)) {
return AppOpsManager.MODE_IGNORED;
}
code = AppOpsManager.opToSwitch(code);
UidState uidState = getUidStateLocked(uid, false);
- // TODO(b/299330771): Check mode for the relevant device.
if (uidState != null
&& mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code)
+ uidState.uid, getPersistentId(virtualDeviceId), code)
!= AppOpsManager.opToDefaultMode(code)) {
final int rawMode =
mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code);
+ uidState.uid, getPersistentId(virtualDeviceId), code);
return raw ? rawMode : uidState.evalMode(code, rawMode);
}
Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
@@ -2683,8 +2769,9 @@ public class AppOpsService extends IAppOpsService.Stub {
mAudioRestrictionManager.setZenModeAudioRestriction(
code, usage, uid, mode, exceptionPackages);
+ // Only notify default device as other devices are unaffected by restriction changes.
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
+ AppOpsService::notifyWatchersOnDefaultDevice, this, code, UID_ANY));
}
@@ -2694,11 +2781,12 @@ public class AppOpsService extends IAppOpsService.Stub {
mAudioRestrictionManager.setCameraAudioRestriction(mode);
+ // Only notify default device as other devices are unaffected by restriction changes.
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this,
+ AppOpsService::notifyWatchersOnDefaultDevice, this,
AppOpsManager.OP_PLAY_AUDIO, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this,
+ AppOpsService::notifyWatchersOnDefaultDevice, this,
AppOpsManager.OP_VIBRATE, UID_ANY));
}
@@ -2761,11 +2849,18 @@ public class AppOpsService extends IAppOpsService.Stub {
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
final int proxiedUid = attributionSource.getNextUid();
+ final int proxyVirtualDeviceId = attributionSource.getDeviceId();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) {
+ Slog.w(TAG, "noteProxyOperationImpl returned MODE_IGNORED as virtualDeviceId "
+ + proxyVirtualDeviceId + " is invalid");
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
+ proxiedPackageName);
+ }
if (!isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))
|| !isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, proxiedAttributionTag,
@@ -2792,8 +2887,9 @@ public class AppOpsService extends IAppOpsService.Stub {
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
- resolveProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
- proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage);
+ resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
+ Process.INVALID_UID, null, null, proxyFlags, !isProxyTrusted,
+ "proxy " + message, shouldCollectMessage);
if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
proxiedPackageName);
@@ -2810,8 +2906,9 @@ public class AppOpsService extends IAppOpsService.Stub {
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
- proxiedAttributionTag, proxyUid, resolveProxyPackageName, proxyAttributionTag,
- proxiedFlags, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolveProxyPackageName,
+ proxyAttributionTag, proxiedFlags, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage);
}
@Override
@@ -2838,6 +2935,13 @@ public class AppOpsService extends IAppOpsService.Stub {
boolean shouldCollectMessage) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG,
+ "checkOperationImpl returned MODE_IGNORED as virtualDeviceId " + virtualDeviceId
+ + " is invalid");
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
+ }
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
// TODO(b/302609140): Remove extra logging after this issue is diagnosed.
if (code == OP_BLUETOOTH_CONNECT) {
@@ -2854,15 +2958,16 @@ public class AppOpsService extends IAppOpsService.Stub {
packageName);
}
return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
- Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ virtualDeviceId, Process.INVALID_UID, null, null,
+ AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage);
}
private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, int proxyUid, String proxyPackageName,
- @Nullable String proxyAttributionTag, @OpFlags int flags,
- boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage) {
+ @Nullable String attributionTag, int virtualDeviceId, int proxyUid,
+ String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
+ boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -2891,8 +2996,8 @@ public class AppOpsService extends IAppOpsService.Stub {
final Ops ops = getOpsLocked(uid, packageName, attributionTag,
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
if (ops == null) {
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_IGNORED);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, AppOpsManager.MODE_IGNORED);
if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + "flags: " +
AppOpsManager.flagsToString(flags));
@@ -2911,7 +3016,8 @@ public class AppOpsService extends IAppOpsService.Stub {
packageName);
}
final Op op = getOpLocked(ops, code, uid, true);
- final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+ final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag,
+ getPersistentId(virtualDeviceId));
if (attributedOp.isRunning()) {
Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
+ code + " startTime of in progress event="
@@ -2920,33 +3026,33 @@ public class AppOpsService extends IAppOpsService.Stub {
final int switchCode = AppOpsManager.opToSwitch(code);
final UidState uidState = ops.uidState;
- if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
+ if (isOpRestrictedLocked(uid, code, packageName, attributionTag, virtualDeviceId,
+ pvr.bypass, false)) {
attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_IGNORED);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, AppOpsManager.MODE_IGNORED);
return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
packageName);
}
- // TODO(b/299330771): Check mode for the relevant device.
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode)
+ uidState.uid, getPersistentId(virtualDeviceId), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
uidState.evalMode(
code,
mAppOpsCheckingService.getUidMode(
uidState.uid,
- PERSISTENT_DEVICE_ID_DEFAULT,
+ getPersistentId(virtualDeviceId),
switchCode));
if (uidMode != AppOpsManager.MODE_ALLOWED) {
if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- uidMode);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, uidMode);
// TODO(b/302609140): Remove extra logging after this issue is diagnosed.
if (code == OP_BLUETOOTH_CONNECT && uidMode == MODE_ERRORED) {
Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
@@ -2969,8 +3075,8 @@ public class AppOpsService extends IAppOpsService.Stub {
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- mode);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, mode);
// TODO(b/302609140): Remove extra logging after this issue is diagnosed.
if (code == OP_BLUETOOTH_CONNECT && mode == MODE_ERRORED) {
Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
@@ -2986,11 +3092,10 @@ public class AppOpsService extends IAppOpsService.Stub {
: "." + attributionTag) + " flags: "
+ AppOpsManager.flagsToString(flags));
}
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_ALLOWED);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED);
attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
- uidState.getState(),
- flags);
+ uidState.getState(), flags);
if (shouldCollectAsyncNotedOp) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
@@ -3314,6 +3419,13 @@ public class AppOpsService extends IAppOpsService.Stub {
int attributionChainId) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG,
+ "startOperationImpl returned MODE_IGNORED as virtualDeviceId " + virtualDeviceId
+ + " is invalid");
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
+ }
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
@@ -3349,9 +3461,9 @@ public class AppOpsService extends IAppOpsService.Stub {
}
return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
- Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
- attributionChainId);
+ virtualDeviceId, Process.INVALID_UID, null, null, OP_FLAG_SELF,
+ startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ attributionFlags, attributionChainId);
}
/** @deprecated Use {@link #startProxyOperationWithState} instead. */
@@ -3390,11 +3502,18 @@ public class AppOpsService extends IAppOpsService.Stub {
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
final int proxiedUid = attributionSource.getNextUid();
+ final int proxyVirtualDeviceId = attributionSource.getDeviceId();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) {
+ Slog.w(TAG, "startProxyOperationImpl returned MODE_IGNORED as virtualDeviceId "
+ + proxyVirtualDeviceId + " is invalid");
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
+ proxiedPackageName);
+ }
if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))
|| !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, proxiedAttributionTag,
@@ -3435,7 +3554,8 @@ public class AppOpsService extends IAppOpsService.Stub {
// Test if the proxied operation will succeed before starting the proxy operation
final SyncNotedAppOp testProxiedOp = startOperationDryRun(code,
proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag,
- resolvedProxyPackageName, proxiedFlags, startIfModeDefault);
+ proxyVirtualDeviceId, resolvedProxyPackageName, proxiedFlags,
+ startIfModeDefault);
if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
return testProxiedOp;
@@ -3445,8 +3565,9 @@ public class AppOpsService extends IAppOpsService.Stub {
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
- resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
- proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
+ resolvedProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
+ Process.INVALID_UID, null, null, proxyFlags,
+ startIfModeDefault, !isProxyTrusted, "proxy " + message,
shouldCollectMessage, proxyAttributionFlags, attributionChainId);
if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
return proxyAppOp;
@@ -3454,9 +3575,9 @@ public class AppOpsService extends IAppOpsService.Stub {
}
return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
- proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
- proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, proxiedAttributionFlags, attributionChainId);
+ proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolvedProxyPackageName,
+ proxyAttributionTag, proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp,
+ message, shouldCollectMessage, proxiedAttributionFlags, attributionChainId);
}
private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
@@ -3464,11 +3585,11 @@ public class AppOpsService extends IAppOpsService.Stub {
}
private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
- @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
- String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
- boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
- int attributionChainId) {
+ @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId,
+ int proxyUid, String proxyPackageName, @Nullable String proxyAttributionTag,
+ @OpFlags int flags, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
+ @Nullable String message, boolean shouldCollectMessage,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3492,8 +3613,8 @@ public class AppOpsService extends IAppOpsService.Stub {
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
if (ops == null) {
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
- attributionChainId);
+ virtualDeviceId, flags, AppOpsManager.MODE_IGNORED, startType,
+ attributionFlags, attributionChainId);
if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + " flags: "
+ AppOpsManager.flagsToString(flags));
@@ -3501,23 +3622,23 @@ public class AppOpsService extends IAppOpsService.Stub {
packageName);
}
final Op op = getOpLocked(ops, code, uid, true);
- final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+ final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag,
+ getPersistentId(virtualDeviceId));
final UidState uidState = ops.uidState;
- isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
- false);
+ isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag,
+ virtualDeviceId, pvr.bypass, false);
final int switchCode = AppOpsManager.opToSwitch(code);
- // TODO(b/299330771): Check mode for the relevant device.
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode)
+ uidState.uid, getPersistentId(virtualDeviceId), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
uidState.evalMode(
code,
mAppOpsCheckingService.getUidMode(
uidState.uid,
- PERSISTENT_DEVICE_ID_DEFAULT,
+ getPersistentId(virtualDeviceId),
switchCode));
if (!shouldStartForMode(uidMode, startIfModeDefault)) {
if (DEBUG) {
@@ -3527,7 +3648,8 @@ public class AppOpsService extends IAppOpsService.Stub {
}
attributedOp.rejected(uidState.getState(), flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, uidMode, startType, attributionFlags, attributionChainId);
+ virtualDeviceId, flags, uidMode, startType, attributionFlags,
+ attributionChainId);
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
} else {
@@ -3547,7 +3669,8 @@ public class AppOpsService extends IAppOpsService.Stub {
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
attributedOp.rejected(uidState.getState(), flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, mode, startType, attributionFlags, attributionChainId);
+ virtualDeviceId, flags, mode, startType, attributionFlags,
+ attributionChainId);
return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
}
@@ -3557,19 +3680,19 @@ public class AppOpsService extends IAppOpsService.Stub {
try {
if (isRestricted) {
attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
+ proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
attributionFlags, attributionChainId);
} else {
attributedOp.started(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
+ proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
attributionFlags, attributionChainId);
startType = START_TYPE_STARTED;
}
} catch (RemoteException e) {
throw new RuntimeException(e);
}
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, virtualDeviceId,
+ flags, isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
attributionChainId);
}
@@ -3590,7 +3713,7 @@ public class AppOpsService extends IAppOpsService.Stub {
* the proxied app can successfully start the operation.
*/
private SyncNotedAppOp startOperationDryRun(int code, int uid,
- @NonNull String packageName, @Nullable String attributionTag,
+ @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId,
String proxyPackageName, @OpFlags int flags,
boolean startIfModeDefault) {
PackageVerificationResult pvr;
@@ -3624,21 +3747,20 @@ public class AppOpsService extends IAppOpsService.Stub {
}
final Op op = getOpLocked(ops, code, uid, true);
final UidState uidState = ops.uidState;
- isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
- false);
+ isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag,
+ virtualDeviceId, pvr.bypass, false);
final int switchCode = AppOpsManager.opToSwitch(code);
- // TODO(b/299330771): Check mode for the relevant device.
// If there is a non-default mode per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (mAppOpsCheckingService.getUidMode(
- uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode)
+ uidState.uid, getPersistentId(virtualDeviceId), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
uidState.evalMode(
code,
mAppOpsCheckingService.getUidMode(
uidState.uid,
- PERSISTENT_DEVICE_ID_DEFAULT,
+ getPersistentId(virtualDeviceId),
switchCode));
if (!shouldStartForMode(uidMode, startIfModeDefault)) {
if (DEBUG) {
@@ -3697,6 +3819,11 @@ public class AppOpsService extends IAppOpsService.Stub {
String attributionTag, int virtualDeviceId) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(virtualDeviceId)) {
+ Slog.w(TAG, "finishOperationImpl was a no-op as virtualDeviceId " + virtualDeviceId
+ + " is invalid");
+ return;
+ }
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return;
}
@@ -3706,7 +3833,8 @@ public class AppOpsService extends IAppOpsService.Stub {
return;
}
- finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
+ finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag,
+ virtualDeviceId);
}
/** @deprecated Use {@link #finishProxyOperationWithState} instead. */
@@ -3731,6 +3859,7 @@ public class AppOpsService extends IAppOpsService.Stub {
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
final int proxiedUid = attributionSource.getNextUid();
+ final int proxyVirtualDeviceId = attributionSource.getDeviceId();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
@@ -3739,6 +3868,11 @@ public class AppOpsService extends IAppOpsService.Stub {
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
+ if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) {
+ Slog.w(TAG, "finishProxyOperationImpl was a no-op as virtualDeviceId "
+ + proxyVirtualDeviceId + " is invalid");
+ return null;
+ }
if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))
|| !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) {
return null;
@@ -3752,7 +3886,7 @@ public class AppOpsService extends IAppOpsService.Stub {
if (!skipProxyOperation) {
finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
- proxyAttributionTag);
+ proxyAttributionTag, proxyVirtualDeviceId);
}
String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -3762,13 +3896,13 @@ public class AppOpsService extends IAppOpsService.Stub {
}
finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
- proxiedAttributionTag);
+ proxiedAttributionTag, proxyVirtualDeviceId);
return null;
}
private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
- String attributionTag) {
+ String attributionTag, int virtualDeviceId) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag);
@@ -3788,7 +3922,9 @@ public class AppOpsService extends IAppOpsService.Stub {
+ attributionTag + ") op=" + AppOpsManager.opToName(code));
return;
}
- final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+ final AttributedOp attributedOp =
+ op.mDeviceAttributedOps.getOrDefault(getPersistentId(virtualDeviceId),
+ new ArrayMap<>()).get(attributionTag);
if (attributedOp == null) {
Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
+ attributionTag + ") op=" + AppOpsManager.opToName(code));
@@ -3805,8 +3941,8 @@ public class AppOpsService extends IAppOpsService.Stub {
}
void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
- String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
- int attributionFlags, int attributionChainId) {
+ String packageName, @Nullable String attributionTag, int virtualDeviceId,
+ boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
ArraySet<ActiveCallback> dispatchedCallbacks = null;
final int callbackListCount = mActiveWatchers.size();
for (int i = 0; i < callbackListCount; i++) {
@@ -3827,13 +3963,14 @@ public class AppOpsService extends IAppOpsService.Stub {
}
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpActiveChanged,
- this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
- attributionFlags, attributionChainId));
+ this, dispatchedCallbacks, code, uid, packageName, attributionTag,
+ virtualDeviceId, active, attributionFlags, attributionChainId));
}
private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
- boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
+ int virtualDeviceId, boolean active, @AttributionFlags int attributionFlags,
+ int attributionChainId) {
// There are features watching for mode changes such as window manager
// and location manager which are in our process. The callbacks in these
// features may require permissions our remote caller does not have.
@@ -3847,7 +3984,7 @@ public class AppOpsService extends IAppOpsService.Stub {
continue;
}
callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
- active, attributionFlags, attributionChainId);
+ virtualDeviceId, active, attributionFlags, attributionChainId);
} catch (RemoteException e) {
/* do nothing */
}
@@ -3858,7 +3995,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
- String attributionTag, @OpFlags int flags, @Mode int result,
+ String attributionTag, int virtualDeviceId, @OpFlags int flags, @Mode int result,
@AppOpsManager.OnOpStartedListener.StartedType int startedType,
@AttributionFlags int attributionFlags, int attributionChainId) {
ArraySet<StartedCallback> dispatchedCallbacks = null;
@@ -3885,13 +4022,14 @@ public class AppOpsService extends IAppOpsService.Stub {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpStarted,
- this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
- result, startedType, attributionFlags, attributionChainId));
+ this, dispatchedCallbacks, code, uid, pkgName, attributionTag, virtualDeviceId,
+ flags, result, startedType, attributionFlags, attributionChainId));
}
private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
- int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
- @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+ int code, int uid, String packageName, String attributionTag, int virtualDeviceId,
+ @OpFlags int flags, @Mode int result,
+ @AppOpsManager.OnOpStartedListener.StartedType int startedType,
@AttributionFlags int attributionFlags, int attributionChainId) {
final long identity = Binder.clearCallingIdentity();
try {
@@ -3902,8 +4040,9 @@ public class AppOpsService extends IAppOpsService.Stub {
if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
continue;
}
- callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
- result, startedType, attributionFlags, attributionChainId);
+ callback.mCallback.opStarted(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, result, startedType, attributionFlags,
+ attributionChainId);
} catch (RemoteException e) {
/* do nothing */
}
@@ -3914,7 +4053,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
- String attributionTag, @OpFlags int flags, @Mode int result) {
+ String attributionTag, int virtualDeviceId, @OpFlags int flags, @Mode int result) {
ArraySet<NotedCallback> dispatchedCallbacks = null;
final int callbackListCount = mNotedWatchers.size();
for (int i = 0; i < callbackListCount; i++) {
@@ -3935,13 +4074,13 @@ public class AppOpsService extends IAppOpsService.Stub {
}
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChecked,
- this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
- result));
+ this, dispatchedCallbacks, code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, result));
}
private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
- int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
- @Mode int result) {
+ int code, int uid, String packageName, String attributionTag, int virtualDeviceId,
+ @OpFlags int flags, @Mode int result) {
// There are features watching for checks in our process. The callbacks in
// these features may require permissions our remote caller does not have.
final long identity = Binder.clearCallingIdentity();
@@ -3953,8 +4092,8 @@ public class AppOpsService extends IAppOpsService.Stub {
if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
continue;
}
- callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
- result);
+ callback.mCallback.opNoted(code, uid, packageName, attributionTag,
+ virtualDeviceId, flags, result);
} catch (RemoteException e) {
/* do nothing */
}
@@ -4028,6 +4167,22 @@ public class AppOpsService extends IAppOpsService.Stub {
watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
}
+ private boolean isValidVirtualDeviceId(int virtualDeviceId) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ return true;
+ }
+ if (mVirtualDeviceManagerInternal == null) {
+ return true;
+ }
+ if (mVirtualDeviceManagerInternal.isValidVirtualDeviceId(virtualDeviceId)) {
+ mKnownDeviceIds.put(virtualDeviceId,
+ mVirtualDeviceManagerInternal.getPersistentIdForDevice(virtualDeviceId));
+ return true;
+ }
+
+ return false;
+ }
+
private void verifyIncomingOp(int op) {
if (op >= 0 && op < AppOpsManager._NUM_OP) {
// Enforce manage appops permission if it's a restricted read op.
@@ -4488,7 +4643,12 @@ public class AppOpsService extends IAppOpsService.Stub {
}
private boolean isOpRestrictedLocked(int uid, int code, String packageName,
- String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
+ String attributionTag, int virtualDeviceId, @Nullable RestrictionBypass appBypass,
+ boolean isCheckOp) {
+ // Restrictions only apply to the default device.
+ if (virtualDeviceId != Context.DEVICE_ID_DEFAULT) {
+ return false;
+ }
int restrictionSetCount = mOpGlobalRestrictions.size();
for (int i = 0; i < restrictionSetCount; i++) {
@@ -4662,7 +4822,10 @@ public class AppOpsService extends IAppOpsService.Stub {
private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
@Nullable String attribution)
throws NumberFormatException, IOException, XmlPullParserException {
- final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
+ // TODO(b/308201969): Update this method when we introduce disk persistence of events
+ // for accesses on external devices.
+ final AttributedOp attributedOp =
+ parent.getOrCreateAttribution(parent, attribution, PERSISTENT_DEVICE_ID_DEFAULT);
final long key = parser.getAttributeLong(null, "n");
final int uidState = extractUidStateFromKey(key);
@@ -5369,18 +5532,23 @@ public class AppOpsService extends IAppOpsService.Stub {
private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
@HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
@NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
- final int numAttributions = op.mAttributions.size();
+ // TODO(b/299330771): Dump data for all devices.
+ ArrayMap<String, AttributedOp> defaultDeviceAttributedOps = op.mDeviceAttributedOps.get(
+ PERSISTENT_DEVICE_ID_DEFAULT);
+
+ final int numAttributions = defaultDeviceAttributedOps.size();
for (int i = 0; i < numAttributions; i++) {
if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
- op.mAttributions.keyAt(i), filterAttributionTag)) {
+ defaultDeviceAttributedOps.keyAt(i), filterAttributionTag)) {
continue;
}
- pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
- dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
- prefix + " ");
+ pw.print(prefix + defaultDeviceAttributedOps.keyAt(i) + "=[\n");
+ dumpStatesLocked(pw, nowElapsed, op, defaultDeviceAttributedOps.keyAt(i), now, sdf,
+ date, prefix + " ");
pw.print(prefix + "]\n");
}
+
}
private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
@@ -5462,8 +5630,11 @@ public class AppOpsService extends IAppOpsService.Stub {
pw.println();
}
}
+ // TODO(b/299330771): Dump running starts for all devices.
+ final AttributedOp attributedOp =
+ op.mDeviceAttributedOps.getOrDefault(PERSISTENT_DEVICE_ID_DEFAULT,
+ new ArrayMap<>()).get(attributionTag);
- final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
if (attributedOp.isRunning()) {
long earliestElapsedTime = Long.MAX_VALUE;
long maxNumStarts = 0;
@@ -5828,7 +5999,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
for (int i=0; i<mUidStates.size(); i++) {
UidState uidState = mUidStates.valueAt(i);
- // TODO(b/299330771): Get modes for all devices.
+ // TODO(b/299330771): Dump modes for all devices.
final SparseIntArray opModes =
mAppOpsCheckingService.getNonDefaultUidModes(
uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT);
@@ -6043,11 +6214,13 @@ public class AppOpsService extends IAppOpsService.Stub {
if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
userHandle)) {
+ // Notify on PERSISTENT_DEVICE_ID_DEFAULT only as only the default device is
+ // affected by restrictions.
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
+ AppOpsService::notifyWatchersOnDefaultDevice, this, code, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::updateStartedOpModeForUser, this, code, restricted,
- userHandle));
+ AppOpsService::updateStartedOpModeForUserForDefaultDevice, this, code,
+ restricted, userHandle));
}
if (restrictionState.isDefault()) {
@@ -6057,7 +6230,8 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
- private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
+ private void updateStartedOpModeForUserForDefaultDevice(int code, boolean restricted,
+ int userId) {
synchronized (AppOpsService.this) {
int numUids = mUidStates.size();
for (int uidNum = 0; uidNum < numUids; uidNum++) {
@@ -6065,12 +6239,13 @@ public class AppOpsService extends IAppOpsService.Stub {
if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
continue;
}
- updateStartedOpModeForUidLocked(code, restricted, uid);
+ updateStartedOpModeForUidForDefaultDeviceLocked(code, restricted, uid);
}
}
}
- private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
+ private void updateStartedOpModeForUidForDefaultDeviceLocked(int code, boolean restricted,
+ int uid) {
UidState uidState = mUidStates.get(uid);
if (uidState == null) {
return;
@@ -6089,9 +6264,11 @@ public class AppOpsService extends IAppOpsService.Stub {
if (mode != MODE_ALLOWED && mode != MODE_FOREGROUND) {
continue;
}
- int numAttrTags = op.mAttributions.size();
- for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
- AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
+ ArrayMap<String, AttributedOp> defaultDeviceAttributedOps = op.mDeviceAttributedOps.get(
+ PERSISTENT_DEVICE_ID_DEFAULT);
+ for (int tagIndex = 0; tagIndex < defaultDeviceAttributedOps.size();
+ tagIndex++) {
+ AttributedOp attrOp = defaultDeviceAttributedOps.valueAt(tagIndex);
if (restricted && attrOp.isRunning()) {
attrOp.pause();
} else if (attrOp.isPaused()) {
@@ -6101,7 +6278,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
- private void notifyWatchersOfChange(int code, int uid) {
+ private void notifyWatchersOnDefaultDevice(int code, int uid) {
final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
synchronized (this) {
modeChangedListenerSet = mOpModeWatchers.get(code);
@@ -6109,8 +6286,7 @@ public class AppOpsService extends IAppOpsService.Stub {
return;
}
}
-
- notifyOpChanged(modeChangedListenerSet, code, uid, null);
+ notifyOpChanged(modeChangedListenerSet, code, uid, null, PERSISTENT_DEVICE_ID_DEFAULT);
}
@Override
@@ -6582,6 +6758,25 @@ public class AppOpsService extends IAppOpsService.Stub {
return packageNames;
}
+ @NonNull private String getPersistentId(int virtualDeviceId) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ return PERSISTENT_DEVICE_ID_DEFAULT;
+ }
+ if (mVirtualDeviceManagerInternal == null) {
+ return PERSISTENT_DEVICE_ID_DEFAULT;
+ }
+ String persistentId =
+ mVirtualDeviceManagerInternal.getPersistentIdForDevice(virtualDeviceId);
+ if (persistentId == null) {
+ persistentId = mKnownDeviceIds.get(virtualDeviceId);
+ }
+ if (persistentId != null) {
+ return persistentId;
+ }
+ throw new IllegalStateException(
+ "Requested persistentId for invalid virtualDeviceId: " + virtualDeviceId);
+ }
+
private final class ClientUserRestrictionState implements DeathRecipient {
private final IBinder token;
@@ -6713,12 +6908,14 @@ public class AppOpsService extends IAppOpsService.Stub {
}
if (restrictionState.setRestriction(code, restricted)) {
+ // Notify on PERSISTENT_DEVICE_ID_DEFAULT only as only the default device is
+ // affected by restrictions.
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, AppOpsService.this, code,
- UID_ANY));
+ AppOpsService::notifyWatchersOnDefaultDevice, AppOpsService.this,
+ code, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::updateStartedOpModeForUser, AppOpsService.this,
- code, restricted, UserHandle.USER_ALL));
+ AppOpsService::updateStartedOpModeForUserForDefaultDevice,
+ AppOpsService.this, code, restricted, UserHandle.USER_ALL));
}
if (restrictionState.isDefault()) {
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 0ded75a848ac..94baf88e4853 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -42,6 +42,7 @@ import java.util.NoSuchElementException;
final class AttributedOp {
private final @NonNull AppOpsService mAppOpsService;
public final @Nullable String tag;
+ public final @NonNull String persistentDeviceId;
public final @NonNull AppOpsService.Op parent;
/**
@@ -81,9 +82,10 @@ final class AttributedOp {
@Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag,
- @NonNull AppOpsService.Op parent) {
+ @NonNull String persistentDeviceId, @NonNull AppOpsService.Op parent) {
mAppOpsService = appOpsService;
this.tag = tag;
+ this.persistentDeviceId = persistentDeviceId;
this.parent = parent;
}
@@ -196,23 +198,26 @@ final class AttributedOp {
*/
public void started(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
- @AppOpsManager.AttributionFlags
- int attributionFlags, int attributionChainId) throws RemoteException {
+ int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId) throws RemoteException {
startedOrPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState, flags, /* triggeredByUidStateChange */ false,
- /* isStarted */ true, attributionFlags, attributionChainId);
+ proxyAttributionTag, proxyVirtualDeviceId, uidState, flags,
+ /* triggeredByUidStateChange */ false, /* isStarted */ true, attributionFlags,
+ attributionChainId);
}
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
- boolean triggeredByUidStateChange, boolean isStarted, @AppOpsManager.AttributionFlags
- int attributionFlags, int attributionChainId) throws RemoteException {
+ int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, boolean triggeredByUidStateChange,
+ boolean isStarted, @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId) throws RemoteException {
if (!triggeredByUidStateChange && !parent.isRunning() && isStarted) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, true, attributionFlags, attributionChainId);
+ parent.packageName, tag, proxyVirtualDeviceId, true, attributionFlags,
+ attributionChainId);
}
if (isStarted && mInProgressEvents == null) {
@@ -227,7 +232,7 @@ final class AttributedOp {
InProgressStartOpEvent event = events.get(clientId);
if (event == null) {
event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
- SystemClock.elapsedRealtime(), clientId, tag,
+ SystemClock.elapsedRealtime(), clientId, tag, proxyVirtualDeviceId,
PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
attributionFlags, attributionChainId);
@@ -320,8 +325,8 @@ final class AttributedOp {
// TODO ntmyren: Also callback for single attribution tag activity changes
if (!triggeredByUidStateChange && !parent.isRunning()) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op,
- parent.uid, parent.packageName, tag, false,
- event.getAttributionFlags(), event.getAttributionChainId());
+ parent.uid, parent.packageName, tag, event.getVirtualDeviceId(),
+ false, event.getAttributionFlags(), event.getAttributionChainId());
}
}
}
@@ -362,11 +367,13 @@ final class AttributedOp {
*/
public void createPaused(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
- @AppOpsManager.AttributionFlags
- int attributionFlags, int attributionChainId) throws RemoteException {
+ int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags,
+ @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId) throws RemoteException {
startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
- uidState, flags, false, false, attributionFlags, attributionChainId);
+ proxyVirtualDeviceId, uidState, flags, false, false,
+ attributionFlags, attributionChainId);
}
/**
@@ -387,7 +394,7 @@ final class AttributedOp {
finishOrPause(event.getClientId(), false, true);
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, false,
+ parent.packageName, tag, event.getVirtualDeviceId(), false,
event.getAttributionFlags(), event.getAttributionChainId());
}
mInProgressEvents = null;
@@ -419,14 +426,15 @@ final class AttributedOp {
event.getAttributionFlags(), event.getAttributionChainId());
if (shouldSendActive) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, true, event.getAttributionFlags(),
- event.getAttributionChainId());
+ parent.packageName, tag, event.getVirtualDeviceId(), true,
+ event.getAttributionFlags(), event.getAttributionChainId());
}
// Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND
// TODO ntmyren: figure out how to get the real mode.
mAppOpsService.scheduleOpStartedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED,
- event.getAttributionFlags(), event.getAttributionChainId());
+ parent.packageName, tag, event.getVirtualDeviceId(), event.getFlags(),
+ MODE_ALLOWED, START_TYPE_RESUMED, event.getAttributionFlags(),
+ event.getAttributionChainId());
}
mPausedInProgressEvents = null;
}
@@ -488,13 +496,15 @@ final class AttributedOp {
// previously removed unfinished start counts back
if (proxy != null) {
startedOrPaused(event.getClientId(), proxy.getUid(),
- proxy.getPackageName(), proxy.getAttributionTag(), newState,
- event.getFlags(), true, isRunning,
+ proxy.getPackageName(), proxy.getAttributionTag(),
+ event.getVirtualDeviceId(), newState, event.getFlags(),
+ true, isRunning,
event.getAttributionFlags(), event.getAttributionChainId());
} else {
startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null,
- newState, event.getFlags(), true, isRunning,
- event.getAttributionFlags(), event.getAttributionChainId());
+ event.getVirtualDeviceId(), newState, event.getFlags(), true,
+ isRunning, event.getAttributionFlags(),
+ event.getAttributionChainId());
}
events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
@@ -508,7 +518,7 @@ final class AttributedOp {
"Cannot switch to new uidState " + newState);
}
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op,
- parent.uid, parent.packageName, tag, false,
+ parent.uid, parent.packageName, tag, event.getVirtualDeviceId(), false,
eventAttributionFlags, eventAttributionChainId);
}
}
@@ -644,6 +654,9 @@ final class AttributedOp {
/** Id of the client that started the event */
private @NonNull IBinder mClientId;
+ /** virtual device id */
+ private int mVirtualDeviceId;
+
/** The attribution tag for this operation */
private @Nullable String mAttributionTag;
@@ -685,7 +698,7 @@ final class AttributedOp {
* @throws RemoteException If the client is dying
*/
InProgressStartOpEvent(long startTime, long startElapsedTime,
- @NonNull IBinder clientId, @Nullable String attributionTag,
+ @NonNull IBinder clientId, int virtualDeviceId, @Nullable String attributionTag,
@NonNull Runnable onDeath, @AppOpsManager.UidState int uidState,
@Nullable AppOpsManager.OpEventProxyInfo proxy, @AppOpsManager.OpFlags int flags,
@AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)
@@ -693,6 +706,7 @@ final class AttributedOp {
mStartTime = startTime;
mStartElapsedTime = startElapsedTime;
mClientId = clientId;
+ mVirtualDeviceId = virtualDeviceId;
mAttributionTag = attributionTag;
mOnDeath = onDeath;
mUidState = uidState;
@@ -737,7 +751,7 @@ final class AttributedOp {
* @throws RemoteException If the client is dying
*/
public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId,
- @Nullable String attributionTag, @NonNull Runnable onDeath,
+ @Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath,
@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
@Nullable AppOpsManager.OpEventProxyInfo proxy,
@AppOpsManager.AttributionFlags int attributionFlags,
@@ -749,6 +763,7 @@ final class AttributedOp {
mClientId = clientId;
mAttributionTag = attributionTag;
mOnDeath = onDeath;
+ mVirtualDeviceId = virtualDeviceId;
mUidState = uidState;
mFlags = flags;
@@ -802,6 +817,11 @@ final class AttributedOp {
return mAttributionChainId;
}
+ /** @return virtual device id for the access */
+ public int getVirtualDeviceId() {
+ return mVirtualDeviceId;
+ }
+
public void setStartTime(long startTime) {
mStartTime = startTime;
}
@@ -824,11 +844,11 @@ final class AttributedOp {
}
InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
- @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid,
- @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
- @AppOpsManager.AttributionFlags
- int attributionFlags, int attributionChainId) throws RemoteException {
+ @Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath,
+ int proxyUid, @Nullable String proxyPackageName,
+ @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags
+ int attributionFlags, int attributionChainId) throws RemoteException {
InProgressStartOpEvent recycled = acquire();
@@ -839,14 +859,15 @@ final class AttributedOp {
}
if (recycled != null) {
- recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath,
- uidState, flags, proxyInfo, attributionFlags, attributionChainId,
+ recycled.reinit(startTime, elapsedTime, clientId, attributionTag, virtualDeviceId,
+ onDeath, uidState, flags, proxyInfo, attributionFlags, attributionChainId,
mOpEventProxyInfoPool);
return recycled;
}
- return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag,
- onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId);
+ return new InProgressStartOpEvent(startTime, elapsedTime, clientId, virtualDeviceId,
+ attributionTag, onDeath, uidState, proxyInfo, flags, attributionFlags,
+ attributionChainId);
}
}
diff --git a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
index 1d1a9e71cc3d..f5f34c14d41a 100644
--- a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
+++ b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
@@ -16,8 +16,11 @@
package com.android.server.appop;
+import android.companion.virtual.VirtualDeviceManager;
import android.os.RemoteException;
+import java.util.Objects;
+
/**
* Listener for mode changes, encapsulates methods that should be triggered in the event of a mode
* change.
@@ -95,6 +98,20 @@ public abstract class OnOpModeChangedListener {
throws RemoteException;
/**
+ * Method that should be triggered when the app-op's mode is changed.
+ * @param op app-op whose mode-change is being listened to.
+ * @param uid user-is associated with the app-op.
+ * @param packageName package name associated with the app-op.
+ * @param persistentDeviceId device associated with the app-op.
+ */
+ public void onOpModeChanged(int op, int uid, String packageName, String persistentDeviceId)
+ throws RemoteException {
+ if (Objects.equals(persistentDeviceId, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {
+ onOpModeChanged(op, uid, packageName);
+ }
+ }
+
+ /**
* Return human readable string representing the listener.
*/
public abstract String toString();
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index aa6a0f1bb55f..fbd32a67fe6c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -112,10 +112,8 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement
getLogger().logOnError(getContext(), getOperationContext(),
errorCode, vendorCode, getTargetUserId());
try {
- if (getListener() != null) {
- mShouldSendErrorToClient = false;
- getListener().onError(getSensorId(), getCookie(), errorCode, vendorCode);
- }
+ mShouldSendErrorToClient = false;
+ getListener().onError(getSensorId(), getCookie(), errorCode, vendorCode);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to invoke sendError", e);
}
@@ -147,9 +145,7 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement
final int errorCode = BiometricConstants.BIOMETRIC_ERROR_CANCELED;
try {
- if (getListener() != null) {
- getListener().onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */);
- }
+ getListener().onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to invoke sendError", e);
}
@@ -181,7 +177,7 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement
}
try {
- if (getListener() != null && shouldSend) {
+ if (shouldSend) {
getListener().onAcquired(getSensorId(), acquiredInfo, vendorCode);
}
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index f9568ea9d72b..506b4562b43d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -265,17 +265,13 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions>
Slog.d(TAG, "Skipping addAuthToken");
}
try {
- if (listener != null) {
- if (!mIsRestricted) {
- listener.onAuthenticationSucceeded(getSensorId(), identifier, byteToken,
- getTargetUserId(), mIsStrongBiometric);
- } else {
- listener.onAuthenticationSucceeded(getSensorId(), null /* identifier */,
- byteToken,
- getTargetUserId(), mIsStrongBiometric);
- }
+ if (!mIsRestricted) {
+ listener.onAuthenticationSucceeded(getSensorId(), identifier, byteToken,
+ getTargetUserId(), mIsStrongBiometric);
} else {
- Slog.e(TAG, "Received successful auth, but client was not listening");
+ listener.onAuthenticationSucceeded(getSensorId(), null /* identifier */,
+ byteToken,
+ getTargetUserId(), mIsStrongBiometric);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify listener", e);
@@ -301,11 +297,7 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions>
}
try {
- if (listener != null) {
- listener.onAuthenticationFailed(getSensorId());
- } else {
- Slog.e(TAG, "Received failed auth, but client was not listening");
- }
+ listener.onAuthenticationFailed(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify listener", e);
mCallback.onClientFinished(this, false);
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 0216e49b531b..a4088524117c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -22,6 +22,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.IBiometricSensorReceiver;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -55,7 +56,7 @@ public abstract class BaseClientMonitor implements IBinder.DeathRecipient {
@Nullable private IBinder mToken;
private long mRequestId;
- @Nullable private ClientMonitorCallbackConverter mListener;
+ @NonNull private ClientMonitorCallbackConverter mListener;
// Currently only used for authentication client. The cookie generated by BiometricService
// is never 0.
private final int mCookie;
@@ -95,7 +96,8 @@ public abstract class BaseClientMonitor implements IBinder.DeathRecipient {
mContext = context;
mToken = token;
mRequestId = -1;
- mListener = listener;
+ mListener = listener == null ? new ClientMonitorCallbackConverter(
+ new IBiometricSensorReceiver.Default()) : listener;
mTargetUserId = userId;
mOwner = owner;
mCookie = cookie;
@@ -199,7 +201,7 @@ public abstract class BaseClientMonitor implements IBinder.DeathRecipient {
}
mToken = null;
if (clearListener) {
- mListener = null;
+ mListener = new ClientMonitorCallbackConverter(new IBiometricSensorReceiver.Default());
}
}
@@ -233,8 +235,8 @@ public abstract class BaseClientMonitor implements IBinder.DeathRecipient {
return mOwner;
}
- @Nullable
- public final ClientMonitorCallbackConverter getListener() {
+ @NonNull
+ protected ClientMonitorCallbackConverter getListener() {
return mListener;
}
@@ -312,9 +314,7 @@ public abstract class BaseClientMonitor implements IBinder.DeathRecipient {
final int errorCode = BiometricConstants.BIOMETRIC_ERROR_CANCELED;
try {
ClientMonitorCallbackConverter listener = getListener();
- if (listener != null) {
- listener.onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */);
- }
+ listener.onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to invoke sendError", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 2c4d30b622ad..8e7004d8fde5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -82,9 +82,7 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En
final ClientMonitorCallbackConverter listener = getListener();
try {
- if (listener != null) {
- listener.onEnrollResult(identifier, remaining);
- }
+ listener.onEnrollResult(identifier, remaining);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 45ffa23dc66a..d2ef2786c252 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -74,13 +74,9 @@ public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier,
if (identifier == null) {
Slog.e(TAG, "identifier was null, skipping onRemove()");
try {
- if (getListener() != null) {
- getListener().onError(getSensorId(), getCookie(),
- BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_REMOVE,
- 0 /* vendorCode */);
- } else {
- Slog.e(TAG, "Error, listener was null, not sending onError callback");
- }
+ getListener().onError(getSensorId(), getCookie(),
+ BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_REMOVE,
+ 0 /* vendorCode */);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to send error to client for onRemoved", e);
}
@@ -93,9 +89,7 @@ public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier,
identifier.getBiometricId());
try {
- if (getListener() != null) {
- getListener().onRemoved(identifier, remaining);
- }
+ getListener().onRemoved(identifier, remaining);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to notify Removed:", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index f35de93af625..415d2942be5c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -337,7 +337,7 @@ public class FaceAuthenticationClient
onAcquiredInternal(acquireInfo, vendorCode, false /* shouldSend */);
final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
- if (shouldSend && getListener() != null) {
+ if (shouldSend) {
try {
getListener().onAuthenticationFrame(frame);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index f5c452992674..5f370f23134c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -152,7 +152,7 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> {
onAcquiredInternal(acquireInfo, vendorCode, false /* shouldSend */);
final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
- if (shouldSend && getListener() != null) {
+ if (shouldSend) {
try {
getListener().onEnrollmentFrame(frame);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index e404bd2be31e..cf45eb8b3178 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -58,11 +58,6 @@ public class FaceGenerateChallengeClient extends GenerateChallengeClient<AidlSes
void onChallengeGenerated(int sensorId, int userId, long challenge) {
try {
final ClientMonitorCallbackConverter listener = getListener();
- if (listener == null) {
- Slog.e(TAG, "Listener is null in onChallengeGenerated");
- mCallback.onClientFinished(this, false /* success */);
- return;
- }
listener.onChallengeGenerated(sensorId, userId, challenge);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
index 981253699322..47aaeec81978 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
@@ -61,9 +61,7 @@ public class FaceGetFeatureClient extends HalClientMonitor<IBiometricsFace> {
@Override
public void unableToStart() {
try {
- if (getListener() != null) {
- getListener().onFeatureGet(false /* success */, new int[0], new boolean[0]);
- }
+ getListener().onFeatureGet(false /* success */, new int[0], new boolean[0]);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send error", e);
}
@@ -85,9 +83,7 @@ public class FaceGetFeatureClient extends HalClientMonitor<IBiometricsFace> {
featureState[0] = result.value;
mValue = result.value;
- if (getListener() != null) {
- getListener().onFeatureGet(result.status == Status.OK, features, featureState);
- }
+ getListener().onFeatureGet(result.status == Status.OK, features, featureState);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to getFeature", e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 6912961ab94b..e0fd44b9f6bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -450,9 +450,7 @@ public class FingerprintAuthenticationClient
pc.major);
}
- if (getListener() != null) {
- getListener().onUdfpsPointerDown(getSensorId());
- }
+ getListener().onUdfpsPointerDown(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
@@ -471,9 +469,7 @@ public class FingerprintAuthenticationClient
session.getSession().onPointerUp(pc.pointerId);
}
- if (getListener() != null) {
- getListener().onUdfpsPointerUp(getSensorId());
- }
+ getListener().onUdfpsPointerUp(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index a7fb7741fee1..cb220b9e1c34 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -175,11 +175,7 @@ public class FingerprintDetectClient extends AcquisitionClient<AidlSession>
vibrateSuccess();
try {
- if (getListener() != null) {
- getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
- } else {
- Slog.e(TAG, "Listener is null!");
- }
+ getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when sending onDetected", e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 3fb9223249b4..225bd594adc6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -305,9 +305,7 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement
pc.major);
}
- if (getListener() != null) {
- getListener().onUdfpsPointerDown(getSensorId());
- }
+ getListener().onUdfpsPointerDown(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send pointer down", e);
}
@@ -325,9 +323,7 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement
session.getSession().onPointerUp(pc.pointerId);
}
- if (getListener() != null) {
- getListener().onUdfpsPointerUp(getSensorId());
- }
+ getListener().onUdfpsPointerUp(getSensorId());
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send pointer up", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index ce693ff58e19..d2f36ce26b0e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -59,11 +59,6 @@ public class FingerprintGenerateChallengeClient extends GenerateChallengeClient<
void onChallengeGenerated(int sensorId, int userId, long challenge) {
try {
final ClientMonitorCallbackConverter listener = getListener();
- if (listener == null) {
- Slog.e(TAG, "Listener is null in onChallengeGenerated");
- mCallback.onClientFinished(this, false /* success */);
- return;
- }
listener.onChallengeGenerated(sensorId, userId, challenge);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 9232e11a05ee..f85794673476 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -28,6 +28,7 @@ import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.IBinder;
@@ -367,7 +368,8 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
final IBinder token = client.getToken();
final long operationId = authClient.getOperationId();
final int cookie = client.getCookie();
- final ClientMonitorCallbackConverter listener = client.getListener();
+ final ClientMonitorCallbackConverter listener = new ClientMonitorCallbackConverter(
+ new IFingerprintServiceReceiver.Default());
final boolean restricted = authClient.isRestricted();
final int statsClient = client.getLogger().getStatsClient();
final boolean isKeyguard = authClient.isKeyguard();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 7a329e9d69e9..60c532c26f5d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -279,12 +279,10 @@ class FingerprintAuthenticationClient
mALSProbeCallback.getProbe().enable();
UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
- if (getListener() != null) {
- try {
- getListener().onUdfpsPointerDown(getSensorId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
+ try {
+ getListener().onUdfpsPointerDown(getSensorId());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
}
}
@@ -295,12 +293,10 @@ class FingerprintAuthenticationClient
mALSProbeCallback.getProbe().disable();
UdfpsHelper.onFingerUp(getFreshDaemon());
- if (getListener() != null) {
- try {
- getListener().onUdfpsPointerUp(getSensorId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
+ try {
+ getListener().onUdfpsPointerUp(getSensorId());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 6e029d2268e2..50e48fe91c56 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -153,12 +153,10 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId());
pm.incrementAuthForUser(getTargetUserId(), authenticated);
- if (getListener() != null) {
- try {
- getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when sending onDetected", e);
- }
+ try {
+ getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when sending onDetected", e);
}
}
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index a7965441274c..458fd82d9a65 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -1292,7 +1292,7 @@ public class CameraServiceProxy extends SystemService
return;
}
if (DEBUG) Slog.v(TAG, "Setting NFC reader mode. enablePolling: " + enablePolling);
- nfcAdapter.setReaderMode(enablePolling);
+ nfcAdapter.setReaderModePollingEnabled(enablePolling);
}
private static int[] toArray(Collection<Integer> c) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 93addcd84d12..e930627b55af 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -872,14 +872,15 @@ public final class DisplayManagerService extends SystemService {
}
@VisibleForTesting
- void performTraversalInternal(SurfaceControl.Transaction t) {
+ void performTraversalInternal(SurfaceControl.Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions) {
synchronized (mSyncRoot) {
if (!mPendingTraversal) {
return;
}
mPendingTraversal = false;
- performTraversalLocked(t);
+ performTraversalLocked(t, displayTransactions);
}
// List is self-synchronized copy-on-write.
@@ -2593,7 +2594,8 @@ public final class DisplayManagerService extends SystemService {
}
}
- private void performTraversalLocked(SurfaceControl.Transaction t) {
+ private void performTraversalLocked(SurfaceControl.Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions) {
// Clear all viewports before configuring displays so that we can keep
// track of which ones we have configured.
clearViewportsLocked();
@@ -2601,9 +2603,11 @@ public final class DisplayManagerService extends SystemService {
// Configure each display device.
mLogicalDisplayMapper.forEachLocked((LogicalDisplay display) -> {
final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+ final SurfaceControl.Transaction displayTransaction =
+ displayTransactions.get(display.getDisplayIdLocked(), t);
if (device != null) {
- configureDisplayLocked(t, device);
- device.performTraversalLocked(t);
+ configureDisplayLocked(displayTransaction, device);
+ device.performTraversalLocked(displayTransaction);
}
});
@@ -4680,8 +4684,9 @@ public final class DisplayManagerService extends SystemService {
}
@Override
- public void performTraversal(SurfaceControl.Transaction t) {
- performTraversalInternal(t);
+ public void performTraversal(SurfaceControl.Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions) {
+ performTraversalInternal(t, displayTransactions);
}
@Override
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 0f40ca082663..1715254a7403 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -16,8 +16,6 @@
package com.android.server.graphics.fonts;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
-
import static com.android.server.graphics.fonts.FontManagerService.SystemFontException;
import android.annotation.NonNull;
@@ -580,11 +578,11 @@ final class UpdatableFontDir {
return null;
}
resolvedFonts.add(new FontConfig.Font(info.mFile, null, info.getPostScriptName(),
- font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(), null));
+ font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(),
+ null /* family name */, FontConfig.Font.VAR_TYPE_AXES_NONE));
}
FontConfig.FontFamily family = new FontConfig.FontFamily(resolvedFonts,
- LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT,
- VARIABLE_FONT_FAMILY_TYPE_NONE);
+ LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT);
return new FontConfig.NamedFamilyList(Collections.singletonList(family),
fontFamily.getName());
}
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 30a4f3134ffd..3c3cfe61c126 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -45,6 +45,13 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
@VisibleForTesting
static final int STATE_WAITING_FOR_REPORT_POWER_STATUS = 1;
+ // State in which the action is delayed. If the action starts and
+ // {@link PowerManager#isInteractive} returns false, it could indicate the beginning of a
+ // standby process. In this scenario, the action will be removed when
+ // {@link HdmiCecLocalDeviceSource#disableDevice} is called, therefore we delay the action.
+ @VisibleForTesting
+ static final int STATE_CHECK_STANDBY_PROCESS_STARTED = 2;
+
// The maximum number of times we send <Give Device Power Status> before we give up.
// We wait up to RESPONSE_TIMEOUT_MS * LOOP_COUNTER_MAX = 20 seconds.
private static final int LOOP_COUNTER_MAX = 10;
@@ -87,6 +94,22 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
boolean start() {
// Because only source device can create this action, it's safe to cast.
mSource = source();
+
+ if (!mSource.mService.getPowerManager().isInteractive()) {
+ Slog.d(TAG, "PowerManager is not interactive. Delay the action to check if standby"
+ + " started!");
+ mState = STATE_CHECK_STANDBY_PROCESS_STARTED;
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ } else {
+ startAction();
+ }
+
+ return true;
+ }
+
+ private void startAction() {
+ Slog.i(TAG, "Start action.");
+
sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress));
boolean is20TargetOnBefore = mIsCec20 && getTargetDevicePowerStatus(mSource, mTargetAddress,
@@ -116,12 +139,11 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
maySendActiveSource();
}
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
- return true;
+ return;
}
}
mState = STATE_WAITING_FOR_REPORT_POWER_STATUS;
addTimer(mState, HdmiConfig.TIMEOUT_MS);
- return true;
}
private void setAndBroadcastActiveSource() {
@@ -174,14 +196,22 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
if (mState != state) {
return;
}
- if (state == STATE_WAITING_FOR_REPORT_POWER_STATUS) {
- if (mPowerStatusCounter++ < LOOP_COUNTER_MAX) {
- queryDevicePowerStatus();
- addTimer(mState, HdmiConfig.TIMEOUT_MS);
- } else {
- // Couldn't wake up the TV for whatever reason. Report failure.
- finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
- }
+ switch (state) {
+ case STATE_WAITING_FOR_REPORT_POWER_STATUS:
+ if (mPowerStatusCounter++ < LOOP_COUNTER_MAX) {
+ queryDevicePowerStatus();
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ } else {
+ // Couldn't wake up the TV for whatever reason. Report failure.
+ finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
+ }
+ return;
+ case STATE_CHECK_STANDBY_PROCESS_STARTED:
+ Slog.d(TAG, "Action was not removed, start the action.");
+ startAction();
+ return;
+ default:
+ return;
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f031b7b677ac..2ea662c40e0b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3185,6 +3185,24 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
}
}
+
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ String ime = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
+ String defaultDeviceIme = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+ if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
+ + " device input method for user " + mSettings.getUserId()
+ + " - restoring " + defaultDeviceIme);
+ }
+ SecureSettingsWrapper.putString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
+ mSettings.getUserId());
+ }
+ }
+
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
@@ -3659,6 +3677,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
Slog.e(TAG, "windowToken cannot be null.");
return InputBindResult.NULL;
}
+ // The user represented by userId, must be running.
+ if (!mUserManagerInternal.isUserRunning(userId)) {
+ // There is a chance that we hit here because of race condition. Let's just
+ // return an error code instead of crashing the caller process, which at
+ // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
+ // important process.
+ Slog.w(TAG, "User #" + userId + " is not running.");
+ return InputBindResult.INVALID_USER;
+ }
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"IMMS.startInputOrWindowGainedFocus");
@@ -3666,20 +3693,43 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
"InputMethodManagerService#startInputOrWindowGainedFocus");
final InputBindResult result;
synchronized (ImfLock.class) {
+ // If the system is not yet ready, we shouldn't be running third party code.
if (!mSystemReady) {
- // If the system is not yet ready, we shouldn't be running third arty code.
return new InputBindResult(
InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
null /* method */, null /* accessibilitySessions */, null /* channel */,
getSelectedMethodIdLocked(), getSequenceNumberLocked(),
false /* isInputMethodSuppressingSpellChecker */);
}
+ final ClientState cs = mClientController.getClient(client.asBinder());
+ if (cs == null) {
+ throw new IllegalArgumentException("Unknown client " + client.asBinder());
+ }
final long ident = Binder.clearCallingIdentity();
try {
+ // Verify if IMMS is in the process of switching user.
+ if (mUserSwitchHandlerTask != null) {
+ // There is already an on-going pending user switch task.
+ final int nextUserId = mUserSwitchHandlerTask.mToUserId;
+ if (userId == nextUserId) {
+ scheduleSwitchUserTaskLocked(userId, cs.mClient);
+ return InputBindResult.USER_SWITCHING;
+ }
+ final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
+ mSettings.getUserId(), false /* enabledOnly */);
+ for (int profileId : profileIdsWithDisabled) {
+ if (profileId == userId) {
+ scheduleSwitchUserTaskLocked(userId, cs.mClient);
+ return InputBindResult.USER_SWITCHING;
+ }
+ }
+ return InputBindResult.INVALID_USER;
+ }
+
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags,
editorInfo, inputConnection, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, userId, imeDispatcher);
+ unverifiedTargetSdkVersion, userId, imeDispatcher, cs);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3708,7 +3758,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
IRemoteInputConnection inputContext,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) {
if (DEBUG) {
Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason="
+ InputMethodDebug.startInputReasonToString(startInputReason)
@@ -3721,23 +3771,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
+ " windowFlags=#" + Integer.toHexString(windowFlags)
+ " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion
+ " userId=" + userId
- + " imeDispatcher=" + imeDispatcher);
- }
-
- if (!mUserManagerInternal.isUserRunning(userId)) {
- // There is a chance that we hit here because of race condition. Let's just
- // return an error code instead of crashing the caller process, which at
- // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
- // important process.
- Slog.w(TAG, "User #" + userId + " is not running.");
- return InputBindResult.INVALID_USER;
- }
-
- final ClientState cs = mClientController.getClient(client.asBinder());
- if (cs == null) {
- throw new IllegalArgumentException("Unknown client " + client.asBinder());
+ + " imeDispatcher=" + imeDispatcher
+ + " cs=" + cs);
}
-
final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
switch (imeClientFocus) {
@@ -3759,24 +3795,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return InputBindResult.INVALID_DISPLAY_ID;
}
- if (mUserSwitchHandlerTask != null) {
- // There is already an on-going pending user switch task.
- final int nextUserId = mUserSwitchHandlerTask.mToUserId;
- if (userId == nextUserId) {
- scheduleSwitchUserTaskLocked(userId, cs.mClient);
- return InputBindResult.USER_SWITCHING;
- }
- final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
- mSettings.getUserId(), false /* enabledOnly */);
- for (int profileId : profileIdsWithDisabled) {
- if (profileId == userId) {
- scheduleSwitchUserTaskLocked(userId, cs.mClient);
- return InputBindResult.USER_SWITCHING;
- }
- }
- return InputBindResult.INVALID_USER;
- }
-
final boolean shouldClearFlag = mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
// In case mShowForced flag affects the next client to keep IME visible, when the current
// client is leaving due to the next focused client, we clear mShowForced flag when the
@@ -5366,7 +5384,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (!setSubtypeOnly) {
// Set InputMethod here
- mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
+ final String imeId = imi != null ? imi.getId() : "";
+ mSettings.putSelectedInputMethod(imeId);
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ mSettings.putSelectedDefaultDeviceInputMethod(imeId);
+ }
}
}
@@ -5509,6 +5531,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return false; // IME is not found or not enabled.
}
settings.putSelectedInputMethod(imeId);
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ settings.putSelectedDefaultDeviceInputMethod(imeId);
+ }
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
return true;
}
@@ -6555,6 +6580,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// Reset selected IME.
settings.putSelectedInputMethod(nextIme);
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ settings.putSelectedDefaultDeviceInputMethod(nextIme);
+ }
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
}
out.println("Reset current and enabled IMEs for user #" + userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index a51002be344f..e444db1b79e8 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -36,7 +36,6 @@ import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import java.util.function.Predicate;
/**
@@ -88,12 +87,6 @@ final class InputMethodSettings {
mMethodMap = methodMap;
mMethodList = methodMap.values();
mUserId = userId;
- String ime = getSelectedInputMethod();
- String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
- if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
- putSelectedInputMethod(defaultDeviceIme);
- putSelectedDefaultDeviceInputMethod(null);
- }
}
@AnyThread
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index f1298945a9b1..2f20bbe7f29c 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -210,6 +210,9 @@ public class ZenModeHelper {
mDefaultConfig = readDefaultConfig(mContext.getResources());
updateDefaultAutomaticRuleNames();
+ if (Flags.modesApi()) {
+ updateDefaultAutomaticRulePolicies();
+ }
mConfig = mDefaultConfig.copy();
synchronized (mConfigsArrayLock) {
mConfigs.put(UserHandle.USER_SYSTEM, mConfig);
@@ -1967,6 +1970,21 @@ public class ZenModeHelper {
}
}
+ // Updates the policies in the default automatic rules (provided via default XML config) to
+ // be fully filled in default values.
+ private void updateDefaultAutomaticRulePolicies() {
+ if (!Flags.modesApi()) {
+ // Should be checked before calling, but just in case.
+ return;
+ }
+ ZenPolicy defaultPolicy = mDefaultConfig.toZenPolicy();
+ for (ZenRule rule : mDefaultConfig.automaticRules.values()) {
+ if (ZenModeConfig.DEFAULT_RULE_IDS.contains(rule.id) && rule.zenPolicy == null) {
+ rule.zenPolicy = defaultPolicy.copy();
+ }
+ }
+ }
+
@VisibleForTesting
protected void applyRestrictions() {
final boolean zenOn = mZenMode != Global.ZEN_MODE_OFF;
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 47967dbcaba2..dbff4423d10c 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -63,4 +63,11 @@ flag {
namespace: "systemui"
description: "Timing test, no functionality"
bug: "316931130"
+}
+
+flag {
+ name: "notification_custom_view_uri_restriction"
+ namespace: "systemui"
+ description: "This flag enables memory restriction of notifications holding custom views with Uri Bitmaps"
+ bug: "270553691"
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 86d05d92c95b..25a39cc8456f 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -257,7 +257,7 @@ final class IdmapManager {
private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage,
@NonNull AndroidPackage overlayPackage, int userId) {
String targetOverlayableName = overlayPackage.getOverlayTargetOverlayableName();
- if (targetOverlayableName != null && !mPackageManager.getNamedActors().isEmpty()) {
+ if (targetOverlayableName != null) {
try {
OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
targetPackage.getPackageName(), targetOverlayableName, userId);
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index a61b03fdbb39..b9464d96a019 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -32,7 +32,6 @@ import static android.os.Process.INVALID_UID;
import static android.os.Trace.TRACE_TAG_RRO;
import static android.os.Trace.traceBegin;
import static android.os.Trace.traceEnd;
-
import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException;
import android.annotation.NonNull;
@@ -363,7 +362,7 @@ public final class OverlayManagerService extends SystemService {
defaultPackages.add(packageName);
}
}
- return defaultPackages.toArray(new String[0]);
+ return defaultPackages.toArray(new String[defaultPackages.size()]);
}
private final class OverlayManagerPackageMonitor extends PackageMonitor {
@@ -1144,10 +1143,9 @@ public final class OverlayManagerService extends SystemService {
};
private static final class PackageManagerHelperImpl implements PackageManagerHelper {
- private static final class PackageStateUsers {
+ private static class PackageStateUsers {
private PackageState mPackageState;
- private Boolean mDefinesOverlayable = null;
- private final ArraySet<Integer> mInstalledUsers = new ArraySet<>();
+ private final Set<Integer> mInstalledUsers = new ArraySet<>();
private PackageStateUsers(@NonNull PackageState packageState) {
this.mPackageState = packageState;
}
@@ -1162,7 +1160,7 @@ public final class OverlayManagerService extends SystemService {
// state may lead to contradictions within OMS. Better then to lag
// behind until all pending intents have been processed.
private final ArrayMap<String, PackageStateUsers> mCache = new ArrayMap<>();
- private final ArraySet<Integer> mInitializedUsers = new ArraySet<>();
+ private final Set<Integer> mInitializedUsers = new ArraySet<>();
PackageManagerHelperImpl(Context context) {
mContext = context;
@@ -1178,7 +1176,8 @@ public final class OverlayManagerService extends SystemService {
*/
@NonNull
public ArrayMap<String, PackageState> initializeForUser(final int userId) {
- if (mInitializedUsers.add(userId)) {
+ if (!mInitializedUsers.contains(userId)) {
+ mInitializedUsers.add(userId);
mPackageManagerInternal.forEachPackageState((packageState -> {
if (packageState.getPkg() != null
&& packageState.getUserStateOrDefault(userId).isInstalled()) {
@@ -1197,11 +1196,13 @@ public final class OverlayManagerService extends SystemService {
return userPackages;
}
- private PackageStateUsers getRawPackageStateForUser(@NonNull final String packageName,
+ @Override
+ @Nullable
+ public PackageState getPackageStateForUser(@NonNull final String packageName,
final int userId) {
final PackageStateUsers pkg = mCache.get(packageName);
if (pkg != null && pkg.mInstalledUsers.contains(userId)) {
- return pkg;
+ return pkg.mPackageState;
}
try {
if (!mPackageManager.isPackageAvailable(packageName, userId)) {
@@ -1215,14 +1216,8 @@ public final class OverlayManagerService extends SystemService {
return addPackageUser(packageName, userId);
}
- @Override
- public PackageState getPackageStateForUser(@NonNull final String packageName,
- final int userId) {
- final PackageStateUsers pkg = getRawPackageStateForUser(packageName, userId);
- return pkg != null ? pkg.mPackageState : null;
- }
-
- private PackageStateUsers addPackageUser(@NonNull final String packageName,
+ @NonNull
+ private PackageState addPackageUser(@NonNull final String packageName,
final int user) {
final PackageState pkg = mPackageManagerInternal.getPackageStateInternal(packageName);
if (pkg == null) {
@@ -1234,20 +1229,20 @@ public final class OverlayManagerService extends SystemService {
}
@NonNull
- private PackageStateUsers addPackageUser(@NonNull final PackageState pkg,
+ private PackageState addPackageUser(@NonNull final PackageState pkg,
final int user) {
PackageStateUsers pkgUsers = mCache.get(pkg.getPackageName());
if (pkgUsers == null) {
pkgUsers = new PackageStateUsers(pkg);
mCache.put(pkg.getPackageName(), pkgUsers);
- } else if (pkgUsers.mPackageState != pkg) {
+ } else {
pkgUsers.mPackageState = pkg;
- pkgUsers.mDefinesOverlayable = null;
}
pkgUsers.mInstalledUsers.add(user);
- return pkgUsers;
+ return pkgUsers.mPackageState;
}
+
@NonNull
private void removePackageUser(@NonNull final String packageName, final int user) {
final PackageStateUsers pkgUsers = mCache.get(packageName);
@@ -1265,15 +1260,15 @@ public final class OverlayManagerService extends SystemService {
}
}
+ @Nullable
public PackageState onPackageAdded(@NonNull final String packageName, final int userId) {
- final var pu = addPackageUser(packageName, userId);
- return pu != null ? pu.mPackageState : null;
+ return addPackageUser(packageName, userId);
}
+ @Nullable
public PackageState onPackageUpdated(@NonNull final String packageName,
final int userId) {
- final var pu = addPackageUser(packageName, userId);
- return pu != null ? pu.mPackageState : null;
+ return addPackageUser(packageName, userId);
}
public void onPackageRemoved(@NonNull final String packageName, final int userId) {
@@ -1313,30 +1308,22 @@ public final class OverlayManagerService extends SystemService {
return (pkgs.length == 0) ? null : pkgs[0];
}
+ @Nullable
@Override
public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
@NonNull String targetOverlayableName, int userId)
throws IOException {
- final var psu = getRawPackageStateForUser(packageName, userId);
- final var pkg = (psu == null || psu.mPackageState == null)
- ? null : psu.mPackageState.getAndroidPackage();
+ var packageState = getPackageStateForUser(packageName, userId);
+ var pkg = packageState == null ? null : packageState.getAndroidPackage();
if (pkg == null) {
throw new IOException("Unable to get target package");
}
- if (Boolean.FALSE.equals(psu.mDefinesOverlayable)) {
- return null;
- }
-
ApkAssets apkAssets = null;
try {
apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(),
ApkAssets.PROPERTY_ONLY_OVERLAYABLES);
- if (psu.mDefinesOverlayable == null) {
- psu.mDefinesOverlayable = apkAssets.definesOverlayable();
- }
- return Boolean.FALSE.equals(psu.mDefinesOverlayable)
- ? null : apkAssets.getOverlayableInfo(targetOverlayableName);
+ return apkAssets.getOverlayableInfo(targetOverlayableName);
} finally {
if (apkAssets != null) {
try {
@@ -1350,29 +1337,24 @@ public final class OverlayManagerService extends SystemService {
@Override
public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
throws IOException {
- final var psu = getRawPackageStateForUser(targetPackageName, userId);
- var pkg = (psu == null || psu.mPackageState == null)
- ? null : psu.mPackageState.getAndroidPackage();
+ var packageState = getPackageStateForUser(targetPackageName, userId);
+ var pkg = packageState == null ? null : packageState.getAndroidPackage();
if (pkg == null) {
throw new IOException("Unable to get target package");
}
- if (psu.mDefinesOverlayable == null) {
- ApkAssets apkAssets = null;
- try {
- apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(),
- ApkAssets.PROPERTY_ONLY_OVERLAYABLES);
- psu.mDefinesOverlayable = apkAssets.definesOverlayable();
- } finally {
- if (apkAssets != null) {
- try {
- apkAssets.close();
- } catch (Throwable ignored) {
- }
+ ApkAssets apkAssets = null;
+ try {
+ apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath());
+ return apkAssets.definesOverlayable();
+ } finally {
+ if (apkAssets != null) {
+ try {
+ apkAssets.close();
+ } catch (Throwable ignored) {
}
}
}
- return psu.mDefinesOverlayable;
}
@Override
@@ -1563,7 +1545,8 @@ public final class OverlayManagerService extends SystemService {
final OverlayPaths frameworkOverlays =
mImpl.getEnabledOverlayPaths("android", userId, false);
for (final String targetPackageName : targetPackageNames) {
- final var list = new OverlayPaths.Builder(frameworkOverlays);
+ final OverlayPaths.Builder list = new OverlayPaths.Builder();
+ list.addAll(frameworkOverlays);
if (!"android".equals(targetPackageName)) {
list.addAll(mImpl.getEnabledOverlayPaths(targetPackageName, userId, true));
}
@@ -1575,21 +1558,17 @@ public final class OverlayManagerService extends SystemService {
final HashSet<String> invalidPackages = new HashSet<>();
pm.setEnabledOverlayPackages(userId, pendingChanges, updatedPackages, invalidPackages);
- if (DEBUG || !invalidPackages.isEmpty()) {
- for (final String targetPackageName : targetPackageNames) {
- if (DEBUG) {
- Slog.d(TAG,
- "-> Updating overlay: target=" + targetPackageName + " overlays=["
- + pendingChanges.get(targetPackageName)
- + "] userId=" + userId);
- }
+ for (final String targetPackageName : targetPackageNames) {
+ if (DEBUG) {
+ Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
+ + pendingChanges.get(targetPackageName)
+ + "] userId=" + userId);
+ }
- if (invalidPackages.contains(targetPackageName)) {
- Slog.e(TAG, TextUtils.formatSimple(
- "Failed to change enabled overlays for %s user %d",
- targetPackageName,
- userId));
- }
+ if (invalidPackages.contains(targetPackageName)) {
+ Slog.e(TAG, TextUtils.formatSimple(
+ "Failed to change enabled overlays for %s user %d", targetPackageName,
+ userId));
}
}
return new ArrayList<>(updatedPackages);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index c1b6ccc7e25c..972c78db9460 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -772,20 +772,24 @@ final class OverlayManagerServiceImpl {
OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName,
final int userId, boolean includeImmutableOverlays) {
- final var paths = new OverlayPaths.Builder();
- mSettings.forEachMatching(userId, null, targetPackageName, oi -> {
+ final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName,
+ userId);
+ final OverlayPaths.Builder paths = new OverlayPaths.Builder();
+ final int n = overlays.size();
+ for (int i = 0; i < n; i++) {
+ final OverlayInfo oi = overlays.get(i);
if (!oi.isEnabled()) {
- return;
+ continue;
}
if (!includeImmutableOverlays && !oi.isMutable) {
- return;
+ continue;
}
if (oi.isFabricated()) {
paths.addNonApkPath(oi.baseCodePath);
} else {
paths.addApkPath(oi.baseCodePath);
}
- });
+ }
return paths.build();
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index b8b49f3eed2f..eae614ac9e77 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -47,7 +47,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
-import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -183,23 +182,6 @@ final class OverlayManagerSettings {
return CollectionUtils.map(items, SettingsItem::getOverlayInfo);
}
- void forEachMatching(int userId, String overlayName, String targetPackageName,
- @NonNull Consumer<OverlayInfo> consumer) {
- for (int i = 0, n = mItems.size(); i < n; i++) {
- final SettingsItem item = mItems.get(i);
- if (item.getUserId() != userId) {
- continue;
- }
- if (overlayName != null && !item.mOverlay.getPackageName().equals(overlayName)) {
- continue;
- }
- if (targetPackageName != null && !item.mTargetPackageName.equals(targetPackageName)) {
- continue;
- }
- consumer.accept(item.getOverlayInfo());
- }
- }
-
ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
final List<SettingsItem> items = selectWhereUser(userId);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 0555d90779e9..0be8e6ed4c8e 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -586,7 +586,7 @@ public class ComputerEngine implements Computer {
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, intent, resolvedType, flags, filterCallingUid);
+ list, false, intent, resolvedType, filterCallingUid);
}
}
} else {
@@ -616,7 +616,7 @@ public class ComputerEngine implements Computer {
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, originalIntent, resolvedType, flags, filterCallingUid);
+ list, false, originalIntent, resolvedType, filterCallingUid);
}
return skipPostResolution ? list : applyPostResolutionFilter(
@@ -700,7 +700,7 @@ public class ComputerEngine implements Computer {
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, intent, resolvedType, flags, callingUid);
+ list, false, intent, resolvedType, callingUid);
}
}
} else {
@@ -712,7 +712,7 @@ public class ComputerEngine implements Computer {
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
- list, false, originalIntent, resolvedType, flags, callingUid);
+ list, false, originalIntent, resolvedType, callingUid);
}
return list;
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index a10bae92aea5..3abf3a5ab4bf 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -16,7 +16,10 @@
package com.android.server.pm;
+import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
+import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
import static android.os.Trace.TRACE_TAG_DALVIK;
+import static android.os.incremental.IncrementalManager.isIncrementalPath;
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
import static com.android.server.pm.ApexManager.ActiveApexInfo;
@@ -27,6 +30,8 @@ import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_MAIN
import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_OTA;
import static com.android.server.pm.PackageManagerService.REASON_CMDLINE;
import static com.android.server.pm.PackageManagerService.REASON_FIRST_BOOT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
import static com.android.server.pm.PackageManagerService.TAG;
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
@@ -45,6 +50,8 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.Flags;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.dex.ArtManager;
@@ -54,6 +61,7 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.Settings.Global;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -1091,4 +1099,73 @@ public final class DexOptHelper {
+ " has unsupported status " + status);
}
}
+
+ /**
+ * Returns DexoptOptions by the given InstallRequest.
+ */
+ static DexoptOptions getDexoptOptionsByInstallRequest(InstallRequest installRequest,
+ DexManager dexManager) {
+ final PackageSetting ps = installRequest.getScannedPackageSetting();
+ final String packageName = ps.getPackageName();
+ final boolean isBackupOrRestore =
+ installRequest.getInstallReason() == INSTALL_REASON_DEVICE_RESTORE
+ || installRequest.getInstallReason() == INSTALL_REASON_DEVICE_SETUP;
+ final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
+ | DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
+ | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE
+ | (isBackupOrRestore ? DexoptOptions.DEXOPT_FOR_RESTORE : 0);
+ // Compute the compilation reason from the installation scenario.
+ final int compilationReason =
+ dexManager.getCompilationReasonForInstallScenario(
+ installRequest.getInstallScenario());
+ return new DexoptOptions(packageName, compilationReason, dexoptFlags);
+ }
+
+ /**
+ * Use ArtService to perform dexopt by the given InstallRequest.
+ */
+ static DexoptResult dexoptPackageUsingArtService(InstallRequest installRequest,
+ DexoptOptions dexoptOptions) {
+ final PackageSetting ps = installRequest.getScannedPackageSetting();
+ final String packageName = ps.getPackageName();
+
+ PackageManagerLocal packageManagerLocal =
+ LocalManagerRegistry.getManager(PackageManagerLocal.class);
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ packageManagerLocal.withFilteredSnapshot()) {
+ boolean ignoreDexoptProfile =
+ (installRequest.getInstallFlags()
+ & PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE)
+ != 0;
+ /*@DexoptFlags*/ int extraFlags =
+ ignoreDexoptProfile && Flags.useArtServiceV2()
+ ? ArtFlags.FLAG_IGNORE_PROFILE
+ : 0;
+ DexoptParams params = dexoptOptions.convertToDexoptParams(extraFlags);
+ DexoptResult dexOptResult = getArtManagerLocal().dexoptPackage(
+ snapshot, packageName, params);
+
+ return dexOptResult;
+ }
+ }
+
+ /**
+ * Returns whether to perform dexopt by the given InstallRequest.
+ */
+ static boolean shouldPerformDexopt(InstallRequest installRequest, DexoptOptions dexoptOptions,
+ Context context) {
+ final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0);
+ final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
+ final PackageSetting ps = installRequest.getScannedPackageSetting();
+ final AndroidPackage pkg = ps.getPkg();
+ final boolean onIncremental = isIncrementalPath(ps.getPathString());
+
+ return (!instantApp || Global.getInt(context.getContentResolver(),
+ Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
+ && pkg != null
+ && !pkg.isDebuggable()
+ && (!onIncremental)
+ && dexoptOptions.isCompilationEnabled()
+ && !isApex;
+ }
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index e70c5ea656a0..28f3d59ee2ec 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -32,8 +32,6 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_SESSION_INVALID;
import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
-import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
-import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
@@ -170,10 +168,7 @@ import com.android.internal.security.VerityUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.server.EventLogTags;
-import com.android.server.LocalManagerRegistry;
import com.android.server.SystemConfig;
-import com.android.server.art.model.ArtFlags;
-import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.pm.Installer.LegacyDexoptDisabledException;
@@ -800,10 +795,27 @@ final class InstallPackageHelper {
"restoreAndPostInstall userId=" + userId + " package=" + request.getPkg());
}
- // A restore should be requested at this point if (a) the install
- // succeeded, (b) the operation is not an update.
+ PackageSetting packageSetting = null;
+
final boolean update = request.isUpdate();
- boolean doRestore = !update && request.getPkg() != null;
+ boolean doRestore = false;
+ if (request.getPkg() != null && !request.isArchived()) {
+ // A restore should be requested at this point:
+ // if the install succeeded and it's not an archived install
+ if (!update) {
+ // AND the operation is not an update,
+ doRestore = true;
+ } else {
+ // OR the package has never been restored.
+ String packageName = request.getPkg().getPackageName();
+ synchronized (mPm.mLock) {
+ packageSetting = mPm.mSettings.getPackageLPr(packageName);
+ if (packageSetting != null && packageSetting.isPendingRestore()) {
+ doRestore = true;
+ }
+ }
+ }
+ }
// Set up the post-install work request bookkeeping. This will be used
// and cleaned up by the post-install event handling regardless of whether
@@ -833,7 +845,13 @@ final class InstallPackageHelper {
doRestore = performRollbackManagerRestore(userId, token, request);
}
- if (!doRestore) {
+ if (doRestore) {
+ if (packageSetting != null) {
+ synchronized (mPm.mLock) {
+ packageSetting.setPendingRestore(false);
+ }
+ }
+ } else {
// No restore possible, or the Backup Manager was mysteriously not
// available -- just fire the post-install work request directly.
if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
@@ -2464,8 +2482,6 @@ final class InstallPackageHelper {
final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
for (ReconciledPackage reconciledPkg : reconciledPackages) {
final InstallRequest installRequest = reconciledPkg.mInstallRequest;
- final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
- final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0);
final PackageSetting ps = installRequest.getScannedPackageSetting();
final String packageName = ps.getPackageName();
final String codePath = ps.getPathString();
@@ -2507,28 +2523,14 @@ final class InstallPackageHelper {
}
}
- // Compute the compilation reason from the installation scenario.
- final int compilationReason =
- mDexManager.getCompilationReasonForInstallScenario(
- installRequest.getInstallScenario());
-
// Construct the DexoptOptions early to see if we should skip running dexopt.
//
// Do not run PackageDexOptimizer through the local performDexOpt
// method because `pkg` may not be in `mPackages` yet.
//
// Also, don't fail application installs if the dexopt step fails.
- final boolean isBackupOrRestore =
- installRequest.getInstallReason() == INSTALL_REASON_DEVICE_RESTORE
- || installRequest.getInstallReason() == INSTALL_REASON_DEVICE_SETUP;
-
- final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
- | DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
- | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE
- | (isBackupOrRestore ? DexoptOptions.DEXOPT_FOR_RESTORE : 0);
- DexoptOptions dexoptOptions =
- new DexoptOptions(packageName, compilationReason, dexoptFlags);
-
+ DexoptOptions dexoptOptions = DexOptHelper.getDexoptOptionsByInstallRequest(
+ installRequest, mDexManager);
// Check whether we need to dexopt the app.
//
// NOTE: it is IMPORTANT to call dexopt:
@@ -2555,16 +2557,9 @@ final class InstallPackageHelper {
//
// TODO(b/174695087): instantApp and onIncremental should be removed and their install
// path moved to SCENARIO_FAST.
- final boolean performDexopt =
- (!instantApp || android.provider.Settings.Global.getInt(
- mContext.getContentResolver(),
- android.provider.Settings.Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
- && pkg != null
- && !pkg.isDebuggable()
- && (!onIncremental)
- && dexoptOptions.isCompilationEnabled()
- && !isApex;
+ final boolean performDexopt = DexOptHelper.shouldPerformDexopt(installRequest,
+ dexoptOptions, mContext);
if (performDexopt) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
@@ -2579,23 +2574,9 @@ final class InstallPackageHelper {
realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
if (useArtService()) {
- PackageManagerLocal packageManagerLocal =
- LocalManagerRegistry.getManager(PackageManagerLocal.class);
- try (PackageManagerLocal.FilteredSnapshot snapshot =
- packageManagerLocal.withFilteredSnapshot()) {
- boolean ignoreDexoptProfile =
- (installRequest.getInstallFlags()
- & PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE)
- != 0;
- /*@DexoptFlags*/ int extraFlags =
- ignoreDexoptProfile && Flags.useArtServiceV2()
- ? ArtFlags.FLAG_IGNORE_PROFILE
- : 0;
- DexoptParams params = dexoptOptions.convertToDexoptParams(extraFlags);
- DexoptResult dexOptResult = DexOptHelper.getArtManagerLocal().dexoptPackage(
- snapshot, packageName, params);
- installRequest.onDexoptFinished(dexOptResult);
- }
+ DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService(
+ installRequest, dexoptOptions);
+ installRequest.onDexoptFinished(dexOptResult);
} else {
try {
mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
@@ -2919,7 +2900,7 @@ final class InstallPackageHelper {
mPm.scheduleDeferredNoKillPostDelete(args);
if (Flags.improveInstallDontKill()) {
synchronized (mPm.mInstallLock) {
- PackageManagerServiceUtils.linkSplitsToOldDirs(mPm.mInstaller,
+ PackageManagerServiceUtils.linkFilesToOldDirs(mPm.mInstaller,
packageName, pkgSetting.getPath(), pkgSetting.getOldPaths());
}
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index bc0173a90989..43328fcfb9a0 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1726,7 +1726,8 @@ public class LauncherAppsService extends SystemService {
.scheme("android-app")
.authority(callingPackage)
.build())
- .setPackage(installerPackageName);
+ .setPackage(installerPackageName)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 474b5907524c..339b1e71c091 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -19,7 +19,6 @@ package com.android.server.pm;
import static android.app.ActivityManager.START_ABORTED;
import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
import static android.app.ActivityManager.START_PERMISSION_DENIED;
-import static android.app.ActivityManager.START_SUCCESS;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
@@ -286,10 +285,16 @@ public class PackageArchiver {
Slog.e(TAG, TextUtils.formatSimple(
"Unexpected error occurred while unarchiving package %s: %s.", packageName,
t.getLocalizedMessage()));
- return START_ABORTED;
}
- return START_SUCCESS;
+ // We return STATUS_ABORTED because:
+ // 1. Archived App is not actually present during activity start. Hence the unarchival
+ // start should be treated as an error code.
+ // 2. STATUS_ABORTED is not visible to the end consumers. Hence, it will not change user
+ // experience.
+ // 3. Returning STATUS_ABORTED helps us avoid manually handling of different cases like
+ // aborting activity options, animations etc in the Windows Manager.
+ return START_ABORTED;
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index dfe705a7a065..b705e84d4dcd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -598,6 +598,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
static final int DEFAULT_FILE_ACCESS_MODE = 0644;
+ static final int DEFAULT_NATIVE_LIBRARY_FILE_ACCESS_MODE = 0755;
+
final Handler mHandler;
final Handler mBackgroundHandler;
@@ -1550,6 +1552,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
pkgSetting
.setPkg(null)
+ // This package was installed as archived. Need to mark it for later restore.
+ .setPendingRestore(true)
.modifyUserState(userId)
.setInstalled(false)
.setArchiveState(archiveState);
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 85316920f363..4f9ed03dc8e7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -23,6 +23,7 @@ import static android.content.pm.SigningDetails.CertCapabilities.SHARED_USER_ID;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
+import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH;
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
@@ -31,6 +32,7 @@ import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED;
import static com.android.server.pm.PackageManagerService.DEFAULT_FILE_ACCESS_MODE;
+import static com.android.server.pm.PackageManagerService.DEFAULT_NATIVE_LIBRARY_FILE_ACCESS_MODE;
import static com.android.server.pm.PackageManagerService.RANDOM_CODEPATH_PREFIX;
import static com.android.server.pm.PackageManagerService.RANDOM_DIR_PREFIX;
import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
@@ -44,7 +46,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.Overridable;
import android.content.Context;
import android.content.Intent;
@@ -198,7 +200,7 @@ public class PackageManagerServiceUtils {
*/
@Overridable
@ChangeId
- @Disabled /* Enforcement reverted in T: b/274147456 */
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
/**
@@ -1192,8 +1194,7 @@ public class PackageManagerServiceUtils {
public static void applyEnforceIntentFilterMatching(
PlatformCompat compat, ComponentResolverApi resolver,
List<ResolveInfo> resolveInfos, boolean isReceiver,
- Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
- int filterCallingUid) {
+ Intent intent, String resolvedType, int filterCallingUid) {
if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
// Do not enforce filter matching when the caller is system or root
@@ -1203,10 +1204,9 @@ public class PackageManagerServiceUtils {
? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
: null;
- final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
-
- final boolean enforce = compat.isChangeEnabledByUidInternal(
- ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, filterCallingUid);
+ final boolean enforce = android.security.Flags.enforceIntentFilterMatch()
+ && compat.isChangeEnabledByUidInternal(
+ ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, filterCallingUid);
for (int i = resolveInfos.size() - 1; i >= 0; --i) {
final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
@@ -1237,8 +1237,7 @@ public class PackageManagerServiceUtils {
boolean match = false;
for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
- if (IntentResolver.intentMatchesFilter(
- intentFilter, intent, resolvedType, defaultOnly)) {
+ if (IntentResolver.intentMatchesFilter(intentFilter, intent, resolvedType)) {
match = true;
break;
}
@@ -1557,7 +1556,7 @@ public class PackageManagerServiceUtils {
return initiatingPackageName == null || SHELL_PACKAGE_NAME.equals(initiatingPackageName);
}
- public static void linkSplitsToOldDirs(@NonNull Installer installer,
+ public static void linkFilesToOldDirs(@NonNull Installer installer,
@NonNull String packageName,
@NonNull File newPath,
@Nullable Set<File> oldPaths) {
@@ -1572,56 +1571,108 @@ public class PackageManagerServiceUtils {
if (filesInNewPath == null || filesInNewPath.length == 0) {
return;
}
- final List<String> splitApkNames = new ArrayList<String>();
- for (int i = 0; i < filesInNewPath.length; i++) {
- if (!filesInNewPath[i].isDirectory() && filesInNewPath[i].toString().endsWith(".apk")) {
- splitApkNames.add(filesInNewPath[i].getName());
+ final List<File> splitApks = new ArrayList<>();
+ for (File file : filesInNewPath) {
+ if (!file.isDirectory() && file.toString().endsWith(".apk")) {
+ splitApks.add(file);
}
}
- final int numSplits = splitApkNames.size();
- if (numSplits == 0) {
+ if (splitApks.isEmpty()) {
return;
}
+ final File[] splitApkNames = splitApks.toArray(new File[0]);
for (File oldPath : oldPaths) {
if (!oldPath.exists()) {
continue;
}
- for (int i = 0; i < numSplits; i++) {
- final String splitApkName = splitApkNames.get(i);
- final File linkedSplit = new File(oldPath, splitApkName);
- if (linkedSplit.exists()) {
- if (DEBUG) {
- Slog.d(PackageManagerService.TAG, "Skipping existing linked split <"
- + linkedSplit + ">");
- }
- continue;
- }
- final File sourceSplit = new File(newPath, splitApkName);
+ linkFilesAndSetModes(installer, packageName, newPath, oldPath, splitApkNames,
+ DEFAULT_FILE_ACCESS_MODE);
+ linkNativeLibraries(installer, packageName, newPath, oldPath, LIB_DIR_NAME);
+ linkNativeLibraries(installer, packageName, newPath, oldPath, LIB64_DIR_NAME);
+ }
+
+ }
+
+ private static void linkNativeLibraries(@NonNull Installer installer,
+ @NonNull String packageName,
+ @NonNull File sourcePath, @NonNull File targetPath,
+ @NonNull String libDirName) {
+ final File sourceLibDir = new File(sourcePath, libDirName);
+ if (!sourceLibDir.exists()) {
+ return;
+ }
+ final File targetLibDir = new File(targetPath, libDirName);
+ if (!targetLibDir.exists()) {
+ try {
+ NativeLibraryHelper.createNativeLibrarySubdir(targetLibDir);
+ } catch (IOException e) {
+ Slog.w(PackageManagerService.TAG, "Failed to create native library dir at <"
+ + targetLibDir + ">", e);
+ return;
+ }
+ }
+ final File[] archs = sourceLibDir.listFiles();
+ if (archs == null) {
+ return;
+ }
+ for (File arch : archs) {
+ final File targetArchDir = new File(targetLibDir, arch.getName());
+ if (!targetArchDir.exists()) {
try {
- installer.linkFile(packageName, splitApkName,
- newPath.getAbsolutePath(), oldPath.getAbsolutePath());
- if (DEBUG) {
- Slog.d(PackageManagerService.TAG, "Linked <"
- + sourceSplit + "> to <" + linkedSplit + ">");
- }
- } catch (Installer.InstallerException e) {
- Slog.w(PackageManagerService.TAG, "Failed to link split <"
- + sourceSplit + " > to <" + linkedSplit + ">", e);
+ NativeLibraryHelper.createNativeLibrarySubdir(targetArchDir);
+ } catch (IOException e) {
+ Slog.w(PackageManagerService.TAG, "Failed to create native library subdir at <"
+ + targetArchDir + ">", e);
continue;
}
- try {
- Os.chmod(linkedSplit.getAbsolutePath(), DEFAULT_FILE_ACCESS_MODE);
- } catch (ErrnoException e) {
- Slog.w(PackageManagerService.TAG, "Failed to set mode for linked split <"
- + linkedSplit + ">", e);
- continue;
+ }
+ final File sourceArchDir = new File(sourceLibDir, arch.getName());
+ final File[] files = sourceArchDir.listFiles();
+ if (files == null || files.length == 0) {
+ continue;
+ }
+ linkFilesAndSetModes(installer, packageName, sourceArchDir, targetArchDir, files,
+ DEFAULT_NATIVE_LIBRARY_FILE_ACCESS_MODE);
+ }
+ }
+
+ // Link the files with specified names from under the sourcePath to be under the targetPath
+ private static void linkFilesAndSetModes(@NonNull Installer installer, String packageName,
+ @NonNull File sourcePath, @NonNull File targetPath, @NonNull File[] files, int mode) {
+ for (File file : files) {
+ final String fileName = file.getName();
+ final File sourceFile = new File(sourcePath, fileName);
+ final File targetFile = new File(targetPath, fileName);
+ if (targetFile.exists()) {
+ if (DEBUG) {
+ Slog.d(PackageManagerService.TAG, "Skipping existing linked file <"
+ + targetFile + ">");
}
- if (!SELinux.restorecon(linkedSplit)) {
- Slog.w(PackageManagerService.TAG, "Failed to restorecon for linked split <"
- + linkedSplit + ">");
+ continue;
+ }
+ try {
+ installer.linkFile(packageName, fileName,
+ sourcePath.getAbsolutePath(), targetPath.getAbsolutePath());
+ if (DEBUG) {
+ Slog.d(PackageManagerService.TAG, "Linked <"
+ + sourceFile + "> to <" + targetFile + ">");
}
+ } catch (Installer.InstallerException e) {
+ Slog.w(PackageManagerService.TAG, "Failed to link native library <"
+ + sourceFile + "> to <" + targetFile + ">", e);
+ continue;
+ }
+ try {
+ Os.chmod(targetFile.getAbsolutePath(), mode);
+ } catch (ErrnoException e) {
+ Slog.w(PackageManagerService.TAG, "Failed to set mode for linked file <"
+ + targetFile + ">", e);
+ continue;
+ }
+ if (!SELinux.restorecon(targetFile)) {
+ Slog.w(PackageManagerService.TAG, "Failed to restorecon for linked file <"
+ + targetFile + ">");
}
}
- //TODO(b/291212866): support native libs
}
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 74c482b47745..f474d32bc427 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -94,7 +94,8 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
INSTALL_PERMISSION_FIXED,
UPDATE_AVAILABLE,
FORCE_QUERYABLE_OVERRIDE,
- SCANNED_AS_STOPPED_SYSTEM_APP
+ SCANNED_AS_STOPPED_SYSTEM_APP,
+ PENDING_RESTORE,
})
public @interface Flags {
}
@@ -102,6 +103,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
private static final int UPDATE_AVAILABLE = 1 << 1;
private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 2;
private static final int SCANNED_AS_STOPPED_SYSTEM_APP = 1 << 3;
+ private static final int PENDING_RESTORE = 1 << 4;
}
private int mBooleans;
@@ -543,6 +545,20 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return mSharedUserAppId > 0;
}
+ /**
+ * @see PackageState#isPendingRestore()
+ */
+ public PackageSetting setPendingRestore(boolean value) {
+ setBoolean(Booleans.PENDING_RESTORE, value);
+ onChanged();
+ return this;
+ }
+
+ @Override
+ public boolean isPendingRestore() {
+ return getBoolean(Booleans.PENDING_RESTORE);
+ }
+
@Override
public String toString() {
return "PackageSetting{"
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index 203e1de61f2f..b664e39cfd1c 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -459,7 +459,7 @@ final class ResolveIntentHelper {
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mPlatformCompat, componentResolver, list, true, intent,
- resolvedType, flags, filterCallingUid);
+ resolvedType, filterCallingUid);
}
}
} else {
@@ -485,7 +485,7 @@ final class ResolveIntentHelper {
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mPlatformCompat, componentResolver,
- list, true, originalIntent, resolvedType, flags, filterCallingUid);
+ list, true, originalIntent, resolvedType, filterCallingUid);
}
return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid,
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 7cf1d3323f05..c7ee6492f595 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4537,6 +4537,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
t.traceBegin("createNewUser-" + userHandle);
Installer.Batch batch = new Installer.Batch();
final boolean skipPackageAllowList = userTypeInstallablePackages == null;
+ // Use the same timestamp for all system apps that are to be installed on the new user
+ final long currentTimeMillis = System.currentTimeMillis();
synchronized (mLock) {
final int size = mPackages.size();
for (int i = 0; i < size; i++) {
@@ -4552,6 +4554,9 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
ps.getPackageName()));
// Only system apps are initially installed.
ps.setInstalled(shouldReallyInstall, userHandle);
+ if (Flags.fixSystemAppsFirstInstallTime() && shouldReallyInstall) {
+ ps.setFirstInstallTime(currentTimeMillis, userHandle);
+ }
// Non-Apex system apps, that are not included in the allowlist in
// initialNonStoppedSystemPackages, should be marked as stopped by default.
@@ -4878,7 +4883,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
@NeverCompile // Avoid size overhead of debugging code.
void dumpPackageLPr(PrintWriter pw, String prefix, String checkinTag,
- ArraySet<String> permissionNames, PackageSetting ps,
+ ArraySet<String> permissionNames, @NonNull PackageSetting ps,
LegacyPermissionState permissionsState, SimpleDateFormat sdf, Date date,
List<UserInfo> users, boolean dumpAll, boolean dumpAllComponents) {
AndroidPackage pkg = ps.getPkg();
@@ -5017,6 +5022,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
pw.print(prefix); pw.print(" privateFlags="); printFlags(pw,
privateFlags, PRIVATE_FLAG_DUMP_SPEC); pw.println();
}
+ if (ps.isPendingRestore()) {
+ pw.print(prefix); pw.print(" pendingRestore=true");
+ pw.println();
+ }
if (!pkg.isUpdatableSystem()) {
pw.print(prefix); pw.print(" updatableSystem=false");
pw.println();
@@ -5227,6 +5236,9 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
pw.print(prefix); pw.print(" privatePkgFlags="); printFlags(pw, ps.getPrivateFlags(),
PRIVATE_FLAG_DUMP_SPEC);
pw.println();
+ if (ps.isPendingRestore()) {
+ pw.print(prefix); pw.println(" pendingRestore=true");
+ }
pw.print(prefix); pw.print(" apexModuleName="); pw.println(ps.getApexModuleName());
if (pkg != null && pkg.getOverlayTarget() != null) {
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index a7ae4ebcb2eb..e0ee199a343d 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -265,6 +265,14 @@ public interface PackageState {
*/
boolean hasSharedUser();
+
+ /**
+ * Whether this app needs to be restore during next install/update.
+ * E.g. if an app was installed as archived and never had a chance to restore its data.
+ * @hide
+ */
+ boolean isPendingRestore();
+
/**
* Retrieves the shared user app ID. Note that the actual shared user data is not available here
* and must be queried separately.
diff --git a/services/core/java/com/android/server/pm/verify/domain/OWNERS b/services/core/java/com/android/server/pm/verify/domain/OWNERS
index c669112e0512..b451fe4b1077 100644
--- a/services/core/java/com/android/server/pm/verify/domain/OWNERS
+++ b/services/core/java/com/android/server/pm/verify/domain/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 36137
+include /PACKAGE_MANAGER_OWNERS
-chiuwinson@google.com
-patb@google.com
-toddke@google.com \ No newline at end of file
+wloh@google.com \ No newline at end of file
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 61348b586aed..f15646f634c2 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -43,6 +43,7 @@ import android.app.AppOpsManagerInternal;
import android.app.KeyguardManager;
import android.app.TaskInfo;
import android.app.compat.CompatChanges;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
@@ -234,7 +235,12 @@ public final class PermissionPolicyService extends SystemService {
this::synchronizeUidPermissionsAndAppOpsAsync);
mAppOpsCallback = new IAppOpsCallback.Stub() {
- public void opChanged(int op, int uid, @Nullable String packageName) {
+ public void opChanged(int op, int uid, @Nullable String packageName,
+ String persistentDeviceId) {
+ if (Objects.equals(persistentDeviceId,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {
+ return;
+ }
if (packageName != null) {
synchronizeUidPermissionsAndAppOpsAsync(uid);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 1fdcc64a90c8..51790b875465 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -75,6 +75,7 @@ import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
+import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
@@ -3779,6 +3780,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
sendSystemKeyToStatusBarAsync(event);
return true;
}
+ case KeyEvent.KEYCODE_SCREENSHOT:
+ if (emojiAndScreenshotKeycodesAvailable() && down && repeatCount == 0) {
+ interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+ }
+ return true;
}
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
@@ -5022,6 +5028,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case KeyEvent.KEYCODE_MACRO_4:
result &= ~ACTION_PASS_TO_USER;
break;
+ case KeyEvent.KEYCODE_EMOJI_PICKER:
+ if (!emojiAndScreenshotKeycodesAvailable()) {
+ // Don't allow EMOJI_PICKER key to be dispatched until flag is released.
+ result &= ~ACTION_PASS_TO_USER;
+ }
+ break;
}
if (useHapticFeedback) {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index b271a03c109d..a4c6959f8ad5 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -18,6 +18,7 @@ package com.android.server.statusbar;
import android.annotation.Nullable;
import android.app.ITransientNotificationCallback;
+import android.content.ComponentName;
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
import android.os.IBinder;
@@ -241,4 +242,17 @@ public interface StatusBarManagerInternal {
* @see com.android.internal.statusbar.IStatusBar#showMediaOutputSwitcher
*/
void showMediaOutputSwitcher(String packageName);
+
+ /**
+ * Add a tile to the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ * @param end if true, the tile will be added at the end. If false, at the beginning.
+ */
+ void addQsTileToFrontOrEnd(ComponentName tile, boolean end);
+
+ /**
+ * Remove the tile from the Quick Settings Panel
+ * @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
+ */
+ void removeQsTile(ComponentName tile);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index b21721ad7b45..49553586ccd7 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -89,6 +89,7 @@ import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
+import android.view.accessibility.Flags;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -834,6 +835,20 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
}
}
+
+ @Override
+ public void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
+ if (Flags.a11yQsShortcut()) {
+ StatusBarManagerService.this.addQsTileToFrontOrEnd(tile, end);
+ }
+ }
+
+ @Override
+ public void removeQsTile(ComponentName tile) {
+ if (Flags.a11yQsShortcut()) {
+ StatusBarManagerService.this.remTile(tile);
+ }
+ }
};
private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() {
@@ -934,11 +949,26 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
public void addTile(ComponentName component) {
+ if (Flags.a11yQsShortcut()) {
+ addQsTileToFrontOrEnd(component, false);
+ } else {
+ enforceStatusBarOrShell();
+
+ if (mBar != null) {
+ try {
+ mBar.addQsTile(component);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+
+ private void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
enforceStatusBarOrShell();
if (mBar != null) {
try {
- mBar.addQsTile(component);
+ mBar.addQsTileToFrontOrEnd(tile, end);
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 3abebf8c381c..d10205401fe7 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -443,6 +443,8 @@ public class TrustAgentWrapper {
mPendingSuccessfulUnlock = false;
}
+ // It's okay to use the "Inner" version of isDeviceLocked since they differ only for
+ // profiles, which cannot be switched to and thus don't support trust agents anyway.
if (mTrustManagerService.isDeviceLockedInner(mUserId)) {
onDeviceLocked();
} else {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index dbf777f48e35..2b05993c86ba 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -188,25 +188,30 @@ public class TrustManagerService extends SystemService {
new SparseArray<>();
/**
- * Stores the locked state for users on the device. There are three different type of users
+ * Stores the locked state for users on the device. There are several different types of users
* which are handled slightly differently:
* <ul>
- * <li> Users with real keyguard
+ * <li> Users with real keyguard:
* These are users who can be switched to ({@link UserInfo#supportsSwitchToByUser()}). Their
* locked state is derived by a combination of user secure state, keyguard state, trust agent
* decision and biometric authentication result. These are updated via
* {@link #refreshDeviceLockedForUser(int)} and result stored in {@link #mDeviceLockedForUser}.
- * <li> Managed profiles with unified challenge
- * Managed profile with unified challenge always shares the same locked state as their parent,
+ * <li> Profiles with unified challenge:
+ * Profiles with a unified challenge always share the same locked state as their parent,
* so their locked state is not recorded in {@link #mDeviceLockedForUser}. Instead,
* {@link ITrustManager#isDeviceLocked(int)} always resolves their parent user handle and
* queries its locked state instead.
- * <li> Managed profiles with separate challenge
- * Locked state for profile with separate challenge is determined by other parts of the
- * framework (mostly PowerManager) and pushed to TrustManagerService via
- * {@link ITrustManager#setDeviceLockedForUser(int, boolean)}. Although in a corner case when
- * the profile has a separate but empty challenge, setting its {@link #mDeviceLockedForUser} to
- * {@code false} is actually done by {@link #refreshDeviceLockedForUser(int)}.
+ * <li> Profiles without unified challenge:
+ * The locked state for profiles that do not have a unified challenge (e.g. they have a
+ * separate challenge from their parent, or they have no parent at all) is determined by other
+ * parts of the framework (mostly PowerManager) and pushed to TrustManagerService via
+ * {@link ITrustManager#setDeviceLockedForUser(int, boolean)}.
+ * However, in the case where such a profile has an empty challenge, setting its
+ * {@link #mDeviceLockedForUser} to {@code false} is actually done by
+ * {@link #refreshDeviceLockedForUser(int)}.
+ * (This serves as a corner case for managed profiles with a separate but empty challenge. It
+ * is always currently the case for Communal profiles, for which having a non-empty challenge
+ * is not currently supported.)
* </ul>
* TODO: Rename {@link ITrustManager#setDeviceLockedForUser(int, boolean)} to
* {@code setDeviceLockedForProfile} to better reflect its purpose. Unifying
@@ -794,7 +799,7 @@ public class TrustManagerService extends SystemService {
/**
* Update the user's locked state. Only applicable to users with a real keyguard
- * ({@link UserInfo#supportsSwitchToByUser}) and unsecured managed profiles.
+ * ({@link UserInfo#supportsSwitchToByUser}) and unsecured profiles.
*
* If this is called due to an unlock operation set unlockedUser to prevent the lock from
* being prematurely reset for that user while keyguard is still in the process of going away.
@@ -826,7 +831,11 @@ public class TrustManagerService extends SystemService {
boolean secure = mLockPatternUtils.isSecure(id);
if (!info.supportsSwitchToByUser()) {
- if (info.isManagedProfile() && !secure) {
+ if (info.isProfile() && !secure
+ && !mLockPatternUtils.isProfileWithUnifiedChallenge(id)) {
+ // Unsecured profiles need to be explicitly set to false.
+ // However, Unified challenge profiles officially shouldn't have a presence in
+ // mDeviceLockedForUser at all, since that's not how they're tracked.
setDeviceLockedForUser(id, false);
}
continue;
@@ -1853,6 +1862,7 @@ public class TrustManagerService extends SystemService {
}
}
+ /** If the userId has a parent, returns that parent's userId. Otherwise userId is returned. */
private int resolveProfileParent(int userId) {
final long identity = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e434df7836c4..089a88691af3 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2791,6 +2791,29 @@ public final class TvInputManagerService extends SystemService {
}
@Override
+ public void notifyTvAdSessionData(
+ IBinder sessionToken, String type, Bundle data, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
+ userId, "notifyTvAdSessionData");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).notifyTvAdSessionData(type, data);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in notifyTvAdSessionData", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public int getClientPid(String sessionId) {
ensureTunerResourceAccessPermission();
final long identity = Binder.clearCallingIdentity();
@@ -4322,6 +4345,23 @@ public final class TvInputManagerService extends SystemService {
}
}
}
+
+ @Override
+ public void onTvInputSessionData(String type, Bundle data) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "onTvInputSessionData()");
+ }
+ if (mSessionState.session == null || mSessionState.client == null) {
+ return;
+ }
+ try {
+ mSessionState.client.onTvInputSessionData(type, data, mSessionState.seq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onTvInputSessionData", e);
+ }
+ }
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index eacd3f8d4d86..ffce50e5bd5e 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -937,6 +937,41 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@Override
+ public void sendAppLinkCommand(String serviceId, Bundle command, int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "sendAppLinkCommand");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ TvAdServiceState adServiceState = userState.mAdServiceMap.get(serviceId);
+ if (adServiceState == null) {
+ Slogf.e(TAG, "failed to sendAppLinkCommand - unknown service id "
+ + serviceId);
+ return;
+ }
+ ComponentName componentName = adServiceState.mInfo.getComponent();
+ AdServiceState serviceState = userState.mAdServiceStateMap.get(componentName);
+ if (serviceState == null) {
+ serviceState = new AdServiceState(componentName, serviceId, resolvedUserId);
+ serviceState.addPendingAppLinkCommand(command);
+ userState.mAdServiceStateMap.put(componentName, serviceState);
+ updateAdServiceConnectionLocked(componentName, resolvedUserId);
+ } else if (serviceState.mService != null) {
+ serviceState.mService.sendAppLinkCommand(command);
+ } else {
+ serviceState.addPendingAppLinkCommand(command);
+ updateAdServiceConnectionLocked(componentName, resolvedUserId);
+ }
+ }
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in sendAppLinkCommand", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void createSession(final ITvAdClient client, final String serviceId, String type,
int seq, int userId) {
final int callingUid = Binder.getCallingUid();
@@ -1320,6 +1355,32 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@Override
+ public void notifyTvInputSessionData(
+ IBinder sessionToken, String type, Bundle data, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "notifyTvInputSessionData(type=%d)", type);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "notifyTvInputSessionData");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ AdSessionState sessionState =
+ getAdSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+ getAdSessionLocked(sessionState).notifyTvInputSessionData(type, data);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifyTvInputSessionData", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void registerCallback(final ITvAdManagerCallback callback, int userId) {
int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
@@ -4358,6 +4419,110 @@ public class TvInteractiveAppManagerService extends SystemService {
}
}
+ @Override
+ public void onRequestCurrentVideoBounds() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestCurrentVideoBounds");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestCurrentVideoBounds(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestCurrentVideoBounds", e);
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCurrentChannelUri() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestCurrentChannelUri");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestCurrentChannelUri(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestCurrentChannelUri", e);
+ }
+ }
+ }
+
+ @Override
+ public void onRequestTrackInfoList() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestTrackInfoList");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestTrackInfoList(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestTrackInfoList", e);
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCurrentTvInputId() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestCurrentTvInputId");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestCurrentTvInputId(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestCurrentTvInputId", e);
+ }
+ }
+ }
+
+
+ @Override
+ public void onRequestSigning(String id, String algorithm, String alias, byte[] data) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestSigning");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestSigning(
+ id, algorithm, alias, data, mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestSigning", e);
+ }
+ }
+ }
+
+ @Override
+ public void onTvAdSessionData(String type, Bundle data) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onTvAdSessionData");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onTvAdSessionData(type, data, mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onTvAdSessionData", e);
+ }
+ }
+ }
+
@GuardedBy("mLock")
private boolean addAdSessionTokenToClientStateLocked(ITvAdSession session) {
try {
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index ed04e5fde024..1383708d8a5c 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -34,7 +34,7 @@ public class VcnContext {
@NonNull private final Looper mLooper;
@NonNull private final VcnNetworkProvider mVcnNetworkProvider;
@NonNull private final FeatureFlags mFeatureFlags;
- @NonNull private final com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;
+ @NonNull private final android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
private final boolean mIsInTestMode;
public VcnContext(
@@ -49,7 +49,7 @@ public class VcnContext {
// Auto-generated class
mFeatureFlags = new FeatureFlagsImpl();
- mCoreNetFeatureFlags = new com.android.net.flags.FeatureFlagsImpl();
+ mCoreNetFeatureFlags = new android.net.platform.flags.FeatureFlagsImpl();
}
@NonNull
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 0a7872f5c25f..c9805c706fbc 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -17,10 +17,10 @@
package com.android.server.vibrator;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.hardware.vibrator.V1_0.EffectStrength;
import android.os.IExternalVibratorService;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.vibrator.Flags;
@@ -57,8 +57,7 @@ final class VibrationScaler {
private final SparseArray<ScaleLevel> mScaleLevels;
private final VibrationSettings mSettingsController;
private final int mDefaultVibrationAmplitude;
-
- private SparseArray<Float> mAdaptiveHapticsScales;
+ private final SparseArray<Float> mAdaptiveHapticsScales = new SparseArray<>();
VibrationScaler(Context context, VibrationSettings settingsController) {
mSettingsController = settingsController;
@@ -147,7 +146,7 @@ final class VibrationScaler {
// If adaptive haptics scaling is available for this usage, apply it to the segment.
if (Flags.adaptiveHapticsEnabled()
- && mAdaptiveHapticsScales != null && mAdaptiveHapticsScales.size() > 0
+ && mAdaptiveHapticsScales.size() > 0
&& mAdaptiveHapticsScales.contains(usageHint)) {
float adaptiveScale = mAdaptiveHapticsScales.get(usageHint);
segment = segment.scale(adaptiveScale);
@@ -187,13 +186,35 @@ final class VibrationScaler {
}
/**
- * Updates the adaptive haptics scales.
- * @param scales the new vibration scales to apply.
+ * Updates the adaptive haptics scales list by adding or modifying the scale for this usage.
+ *
+ * @param usageHint one of VibrationAttributes.USAGE_*.
+ * @param scale The scaling factor that should be applied to vibrations of this usage.
+ *
+ * @hide
+ */
+ public void updateAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint, float scale) {
+ mAdaptiveHapticsScales.put(usageHint, scale);
+ }
+
+ /**
+ * Removes the usage from the cached adaptive haptics scales list.
+ *
+ * @param usageHint one of VibrationAttributes.USAGE_*.
+ *
+ * @hide
+ */
+ public void removeAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint) {
+ mAdaptiveHapticsScales.remove(usageHint);
+ }
+
+ /**
+ * Removes all cached adaptive haptics scales.
*
* @hide
*/
- public void updateAdaptiveHapticsScales(@Nullable SparseArray<Float> scales) {
- mAdaptiveHapticsScales = scales;
+ public void clearAdaptiveHapticsScales() {
+ mAdaptiveHapticsScales.clear();
}
/** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 839c2075fa8c..fab0430cc40d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -376,6 +376,25 @@ final class VibrationSettings {
}
/**
+ * Returns the duration, in milliseconds, that the vibrator control service will wait for new
+ * vibration params.
+ * @return The request vibration params timeout in milliseconds.
+ * @hide
+ */
+ public int getRequestVibrationParamsTimeoutMs() {
+ return mVibrationConfig.getRequestVibrationParamsTimeoutMs();
+ }
+
+ /**
+ * The list of usages that should request vibration params before they are played. These
+ * usages don't have strong latency requirements, e.g. ringtone and notification, and can be
+ * slightly delayed.
+ */
+ public int[] getRequestVibrationParamsForUsages() {
+ return mVibrationConfig.getRequestVibrationParamsForUsages();
+ }
+
+ /**
* Return a {@link VibrationEffect} that should be played if the device do not support given
* {@code effectId}.
*
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 624da8011163..9cf942e91439 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -21,7 +21,9 @@ import android.annotation.Nullable;
import android.os.Build;
import android.os.CombinedVibration;
import android.os.IBinder;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
@@ -38,6 +40,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
/**
* Creates and manages a queue of steps for performing a VibrationEffect, as well as coordinating
@@ -65,15 +69,18 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
private final DeviceAdapter mDeviceAdapter;
+ private final VibrationScaler mVibrationScaler;
// Not guarded by lock because it's mostly used to read immutable fields by this conductor.
// This is only modified here at the prepareToStart method which always runs at the vibration
// thread, to update the adapted effect and report start time.
private final HalVibration mVibration;
-
private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
+ @Nullable
+ private final CompletableFuture<Void> mRequestVibrationParamsFuture;
+
// Signalling fields.
// Note that vibrator callback signals may happen inside vibrator HAL calls made by the
// VibrationThread, or on an external executor, so this lock should not be held for anything
@@ -97,10 +104,14 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
DeviceAdapter deviceAdapter,
+ VibrationScaler vibrationScaler,
+ CompletableFuture<Void> requestVibrationParamsFuture,
VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
this.mVibration = vib;
this.vibrationSettings = vibrationSettings;
this.mDeviceAdapter = deviceAdapter;
+ mVibrationScaler = vibrationScaler;
+ mRequestVibrationParamsFuture = requestVibrationParamsFuture;
this.vibratorManagerHooks = vibratorManagerHooks;
this.mSignalVibratorsComplete =
new IntArray(mDeviceAdapter.getAvailableVibratorIds().length);
@@ -143,7 +154,15 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(true);
}
- // Scaling happened before the effect was dispatched to this conductor (or to input devices)
+
+ if (!mVibration.callerInfo.attrs.isFlagSet(
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
+ if (Flags.adaptiveHapticsEnabled()) {
+ waitForVibrationParamsIfRequired();
+ }
+ mVibration.scaleEffects(mVibrationScaler::scale);
+ }
+
mVibration.adaptToDevice(mDeviceAdapter);
CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffectToPlay());
mPendingVibrateSteps++;
@@ -361,6 +380,9 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
+ mSignalCancelImmediate);
}
}
+ if (mRequestVibrationParamsFuture != null) {
+ mRequestVibrationParamsFuture.cancel(/* mayInterruptIfRunning= */true);
+ }
mLock.notify();
}
}
@@ -420,6 +442,30 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
}
}
+ /**
+ * Blocks until the vibration params future is complete.
+ *
+ * This should be called by the VibrationThread and may be interrupted by calling
+ * `notifyCancelled` from outside it.
+ */
+ private void waitForVibrationParamsIfRequired() {
+ if (Build.IS_DEBUGGABLE) {
+ expectIsVibrationThread(true);
+ }
+
+ if (mRequestVibrationParamsFuture == null) {
+ return;
+ }
+
+ try {
+ mRequestVibrationParamsFuture.orTimeout(
+ vibrationSettings.getRequestVibrationParamsTimeoutMs(),
+ TimeUnit.MILLISECONDS).get();
+ } catch (Throwable e) {
+ Slog.w(TAG, "Failed to retrieve vibration params.", e);
+ }
+ }
+
@GuardedBy("mLock")
private boolean hasPendingNotifySignalLocked() {
if (Build.IS_DEBUGGABLE) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 9d75249abdd9..8f8fe3cd48f5 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -16,11 +16,13 @@
package com.android.server.vibrator;
+import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
import static android.os.VibrationAttributes.USAGE_ALARM;
import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
import static android.os.VibrationAttributes.USAGE_MEDIA;
import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
import static android.os.VibrationAttributes.USAGE_RINGTONE;
import static android.os.VibrationAttributes.USAGE_TOUCH;
import static android.os.VibrationAttributes.USAGE_UNKNOWN;
@@ -32,12 +34,18 @@ import android.frameworks.vibrator.IVibratorControlService;
import android.frameworks.vibrator.IVibratorController;
import android.frameworks.vibrator.ScaleParam;
import android.frameworks.vibrator.VibrationParam;
+import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.VibrationAttributes;
import android.util.Slog;
-import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
/**
* Implementation of {@link IVibratorControlService} which allows the registration of
@@ -47,16 +55,25 @@ import java.util.Objects;
*/
public final class VibratorControlService extends IVibratorControlService.Stub {
private static final String TAG = "VibratorControlService";
+ private static final int UNRECOGNIZED_VIBRATION_TYPE = -1;
+ private static final int NO_SCALE = -1;
private final VibratorControllerHolder mVibratorControllerHolder;
private final VibrationScaler mVibrationScaler;
private final Object mLock;
+ private final int[] mRequestVibrationParamsForUsages;
+
+ @GuardedBy("mLock")
+ private CompletableFuture<Void> mRequestVibrationParamsFuture = null;
+ @GuardedBy("mLock")
+ private IBinder mRequestVibrationParamsToken;
public VibratorControlService(VibratorControllerHolder vibratorControllerHolder,
- VibrationScaler vibrationScaler, Object lock) {
+ VibrationScaler vibrationScaler, VibrationSettings vibrationSettings, Object lock) {
mVibratorControllerHolder = vibratorControllerHolder;
mVibrationScaler = vibrationScaler;
mLock = lock;
+ mRequestVibrationParamsForUsages = vibrationSettings.getRequestVibrationParamsForUsages();
}
@Override
@@ -85,8 +102,9 @@ public final class VibratorControlService extends IVibratorControlService.Stub {
+ "controller doesn't match the registered one. " + this);
return;
}
- updateAdaptiveHapticsScales(/* params= */ null);
+ mVibrationScaler.clearAdaptiveHapticsScales();
mVibratorControllerHolder.setVibratorController(null);
+ endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
}
}
@@ -131,10 +149,8 @@ public final class VibratorControlService extends IVibratorControlService.Stub {
+ "controller doesn't match the registered one. " + this);
return;
}
- //TODO(305942827): Update this method to only clear the specified vibration types.
- // Perhaps look into whether it makes more sense to have this clear all scales and
- // rely on setVibrationParams for clearing the scales for specific vibrations.
- updateAdaptiveHapticsScales(/* params= */ null);
+
+ updateAdaptiveHapticsScales(types, NO_SCALE);
}
}
@@ -142,7 +158,26 @@ public final class VibratorControlService extends IVibratorControlService.Stub {
public void onRequestVibrationParamsComplete(
@NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)
throws RemoteException {
- // TODO(305942827): Cache the vibration params in VibrationScaler
+ Objects.requireNonNull(requestToken);
+
+ synchronized (mLock) {
+ if (mRequestVibrationParamsToken == null) {
+ Slog.wtf(TAG,
+ "New vibration params received but no token was cached in the service. "
+ + "New vibration params ignored.");
+ return;
+ }
+
+ if (!Objects.equals(requestToken, mRequestVibrationParamsToken)) {
+ Slog.w(TAG,
+ "New vibration params received but the provided token does not match the "
+ + "cached one. New vibration params ignored.");
+ return;
+ }
+
+ updateAdaptiveHapticsScales(result);
+ endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false);
+ }
}
@Override
@@ -156,50 +191,190 @@ public final class VibratorControlService extends IVibratorControlService.Stub {
}
/**
- * Extracts the vibration scales and caches them in {@link VibrationScaler}.
+ * If an {@link IVibratorController} is registered to the service, it will request the latest
+ * vibration params and return a {@link CompletableFuture} that completes when the request is
+ * fulfilled. Otherwise, ignores the call and returns null.
*
- * @param params the new vibration params to cache.
+ * @param usage a {@link android.os.VibrationAttributes} usage.
+ * @param timeoutInMillis the request's timeout in millis.
+ * @return a {@link CompletableFuture} to track the completion of the vibration param
+ * request, or null if no {@link IVibratorController} is registered.
*/
- private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) {
- if (params == null || params.length == 0) {
- mVibrationScaler.updateAdaptiveHapticsScales(null);
+ @Nullable
+ public CompletableFuture<Void> triggerVibrationParamsRequest(
+ @VibrationAttributes.Usage int usage, int timeoutInMillis) {
+ synchronized (mLock) {
+ IVibratorController vibratorController =
+ mVibratorControllerHolder.getVibratorController();
+ if (vibratorController == null) {
+ Slog.d(TAG, "Unable to request vibration params. There is no registered "
+ + "IVibrationController.");
+ return null;
+ }
+
+ int vibrationType = mapToAdaptiveVibrationType(usage);
+ if (vibrationType == UNRECOGNIZED_VIBRATION_TYPE) {
+ Slog.d(TAG, "Unable to request vibration params. The provided usage " + usage
+ + " is unrecognized.");
+ return null;
+ }
+
+ try {
+ endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
+ mRequestVibrationParamsFuture = new CompletableFuture<>();
+ mRequestVibrationParamsToken = new Binder();
+ vibratorController.requestVibrationParams(vibrationType, timeoutInMillis,
+ mRequestVibrationParamsToken);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to request vibration params.", e);
+ endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
+ }
+
+ return mRequestVibrationParamsFuture;
+ }
+ }
+
+ /**
+ * If an {@link IVibratorController} is registered to the service, then it checks whether to
+ * request new vibration params before playing the vibration. Returns true if the
+ * usage is for high latency vibrations, e.g. ringtone and notification, and can be delayed
+ * slightly. Otherwise, returns false.
+ *
+ * @param usage a {@link android.os.VibrationAttributes} usage.
+ * @return true if usage is for high latency vibrations, false otherwise.
+ */
+ public boolean shouldRequestVibrationParams(@VibrationAttributes.Usage int usage) {
+ synchronized (mLock) {
+ IVibratorController vibratorController =
+ mVibratorControllerHolder.getVibratorController();
+ if (vibratorController == null) {
+ Slog.d(TAG, "Unable to check if should request vibration params. "
+ + "There is no registered IVibrationController.");
+ return false;
+ }
+
+ return ArrayUtils.contains(mRequestVibrationParamsForUsages, usage);
+ }
+ }
+
+ /**
+ * Returns the {@link #mRequestVibrationParamsToken} which is used to validate
+ * {@link #onRequestVibrationParamsComplete(IBinder, VibrationParam[])} calls.
+ */
+ @VisibleForTesting
+ public IBinder getRequestVibrationParamsToken() {
+ synchronized (mLock) {
+ return mRequestVibrationParamsToken;
+ }
+ }
+
+ /**
+ * Completes or cancels the vibration params request future and resets the future and token
+ * to null.
+ * @param wasCancelled specifies whether the future should be ended by being cancelled or not.
+ */
+ @GuardedBy("mLock")
+ private void endOngoingRequestVibrationParamsLocked(boolean wasCancelled) {
+ mRequestVibrationParamsToken = null;
+ if (mRequestVibrationParamsFuture == null) {
return;
}
- SparseArray<Float> vibrationScales = new SparseArray<>();
- for (int i = 0; i < params.length; i++) {
- ScaleParam scaleParam = params[i].getScale();
- extractVibrationScales(scaleParam, vibrationScales);
+ if (wasCancelled) {
+ mRequestVibrationParamsFuture.cancel(/* mayInterruptIfRunning= */ true);
+ } else {
+ mRequestVibrationParamsFuture.complete(null);
+ }
+
+ mRequestVibrationParamsFuture = null;
+ }
+
+ private static int mapToAdaptiveVibrationType(@VibrationAttributes.Usage int usage) {
+ switch (usage) {
+ case USAGE_ALARM -> {
+ return ScaleParam.TYPE_ALARM;
+ }
+ case USAGE_NOTIFICATION, USAGE_COMMUNICATION_REQUEST -> {
+ return ScaleParam.TYPE_NOTIFICATION;
+ }
+ case USAGE_RINGTONE -> {
+ return ScaleParam.TYPE_RINGTONE;
+ }
+ case USAGE_MEDIA, USAGE_UNKNOWN -> {
+ return ScaleParam.TYPE_MEDIA;
+ }
+ case USAGE_TOUCH, USAGE_HARDWARE_FEEDBACK, USAGE_ACCESSIBILITY,
+ USAGE_PHYSICAL_EMULATION -> {
+ return ScaleParam.TYPE_INTERACTIVE;
+ }
+ default -> {
+ Slog.w(TAG, "Unrecognized vibration usage " + usage);
+ return UNRECOGNIZED_VIBRATION_TYPE;
+ }
}
- mVibrationScaler.updateAdaptiveHapticsScales(vibrationScales);
}
/**
- * Extracts the vibration scales and map them to their corresponding
- * {@link android.os.VibrationAttributes} usages.
+ * Updates the adaptive haptics scales cached in {@link VibrationScaler} with the
+ * provided params.
+ *
+ * @param params the new vibration params.
+ */
+ private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) {
+ for (VibrationParam param : params) {
+ ScaleParam scaleParam = param.getScale();
+ updateAdaptiveHapticsScales(scaleParam.typesMask, scaleParam.scale);
+ }
+ }
+
+ /**
+ * Updates the adaptive haptics scales, cached in {@link VibrationScaler}, for the provided
+ * vibration types.
+ *
+ * @param types The type of vibrations.
+ * @param scale The scaling factor that should be applied to the vibrations.
*/
- private void extractVibrationScales(ScaleParam scaleParam, SparseArray<Float> vibrationScales) {
- if ((ScaleParam.TYPE_ALARM & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_ALARM, scaleParam.scale);
+ private void updateAdaptiveHapticsScales(int types, float scale) {
+ if ((ScaleParam.TYPE_ALARM & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_ALARM, scale);
}
- if ((ScaleParam.TYPE_NOTIFICATION & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_NOTIFICATION, scaleParam.scale);
- vibrationScales.put(USAGE_COMMUNICATION_REQUEST, scaleParam.scale);
+ if ((ScaleParam.TYPE_NOTIFICATION & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_NOTIFICATION, scale);
+ updateOrRemoveAdaptiveHapticsScale(USAGE_COMMUNICATION_REQUEST, scale);
}
- if ((ScaleParam.TYPE_RINGTONE & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_RINGTONE, scaleParam.scale);
+ if ((ScaleParam.TYPE_RINGTONE & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_RINGTONE, scale);
}
- if ((ScaleParam.TYPE_MEDIA & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_MEDIA, scaleParam.scale);
- vibrationScales.put(USAGE_UNKNOWN, scaleParam.scale);
+ if ((ScaleParam.TYPE_MEDIA & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_MEDIA, scale);
+ updateOrRemoveAdaptiveHapticsScale(USAGE_UNKNOWN, scale);
}
- if ((ScaleParam.TYPE_INTERACTIVE & scaleParam.typesMask) != 0) {
- vibrationScales.put(USAGE_TOUCH, scaleParam.scale);
- vibrationScales.put(USAGE_HARDWARE_FEEDBACK, scaleParam.scale);
+ if ((ScaleParam.TYPE_INTERACTIVE & types) != 0) {
+ updateOrRemoveAdaptiveHapticsScale(USAGE_TOUCH, scale);
+ updateOrRemoveAdaptiveHapticsScale(USAGE_HARDWARE_FEEDBACK, scale);
}
}
+
+ /**
+ * Updates or removes the adaptive haptics scale for the specified usage. If the scale is set
+ * to {@link #NO_SCALE} then it will be removed from the cached usage scales in
+ * {@link VibrationScaler}. Otherwise, the cached usage scale will be updated by the new value.
+ *
+ * @param usageHint one of VibrationAttributes.USAGE_*.
+ * @param scale The scaling factor that should be applied to the vibrations. If set to
+ * {@link #NO_SCALE} then the scale will be removed.
+ */
+ private void updateOrRemoveAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint,
+ float scale) {
+ if (scale == NO_SCALE) {
+ mVibrationScaler.removeAdaptiveHapticsScale(usageHint);
+ return;
+ }
+
+ mVibrationScaler.updateAdaptiveHapticsScale(usageHint, scale);
+ }
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
index 63e69db9480f..79a99b3ee2ff 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
@@ -57,7 +57,7 @@ public final class VibratorControllerHolder implements IBinder.DeathRecipient {
@Override
public void binderDied(@NonNull IBinder deadBinder) {
- if (deadBinder == mVibratorController.asBinder()) {
+ if (mVibratorController != null && deadBinder == mVibratorController.asBinder()) {
setVibratorController(null);
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 2c1ab955514e..759450b528b9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -53,6 +53,7 @@ import android.os.Trace;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.os.vibrator.VibratorInfoFactory;
@@ -84,6 +85,7 @@ import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -161,6 +163,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private final VibrationSettings mVibrationSettings;
private final VibrationScaler mVibrationScaler;
+ private final VibratorControlService mVibratorControlService;
private final InputDeviceDelegate mInputDeviceDelegate;
private final DeviceAdapter mDeviceAdapter;
@@ -212,6 +215,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mVibrationSettings = new VibrationSettings(mContext, mHandler);
mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
+ mVibratorControlService = new VibratorControlService(
+ injector.createVibratorControllerHolder(), mVibrationScaler, mVibrationSettings,
+ mLock);
mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
VibrationCompleteListener listener = new VibrationCompleteListener(this);
@@ -272,9 +278,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
if (ServiceManager.isDeclared(VIBRATOR_CONTROL_SERVICE)) {
- injector.addService(VIBRATOR_CONTROL_SERVICE,
- new VibratorControlService(new VibratorControllerHolder(), mVibrationScaler,
- mLock));
+ injector.addService(VIBRATOR_CONTROL_SERVICE, mVibratorControlService);
}
}
@@ -783,19 +787,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private Vibration.EndInfo startVibrationLocked(HalVibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
- if (!vib.callerInfo.attrs.isFlagSet(
- VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
- // Scale effect before dispatching it to the input devices or the vibration thread.
- vib.scaleEffects(mVibrationScaler::scale);
- }
- boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
- vib.callerInfo, vib.getEffectToPlay());
- if (inputDevicesAvailable) {
- return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
+ if (mInputDeviceDelegate.isAvailable()) {
+ return startVibrationOnInputDevicesLocked(vib);
}
- VibrationStepConductor conductor = new VibrationStepConductor(vib, mVibrationSettings,
- mDeviceAdapter, mVibrationThreadCallbacks);
+ VibrationStepConductor conductor = createVibrationStepConductor(vib);
+
if (mCurrentVibration == null) {
return startVibrationOnThreadLocked(conductor);
}
@@ -866,6 +863,34 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
}
+ private VibrationStepConductor createVibrationStepConductor(HalVibration vib) {
+ CompletableFuture<Void> requestVibrationParamsFuture = null;
+
+ if (Flags.adaptiveHapticsEnabled() && !vib.callerInfo.attrs.isFlagSet(
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)
+ && mVibratorControlService.shouldRequestVibrationParams(
+ vib.callerInfo.attrs.getUsage())) {
+ requestVibrationParamsFuture =
+ mVibratorControlService.triggerVibrationParamsRequest(
+ vib.callerInfo.attrs.getUsage(),
+ mVibrationSettings.getRequestVibrationParamsTimeoutMs());
+ }
+
+ return new VibrationStepConductor(vib, mVibrationSettings, mDeviceAdapter, mVibrationScaler,
+ requestVibrationParamsFuture, mVibrationThreadCallbacks);
+ }
+
+ private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
+ if (!vib.callerInfo.attrs.isFlagSet(
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
+ // Scale effect before dispatching it to the input devices.
+ vib.scaleEffects(mVibrationScaler::scale);
+ }
+ mInputDeviceDelegate.vibrateIfAvailable(vib.callerInfo, vib.getEffectToPlay());
+
+ return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
+ }
+
private void logVibrationStatus(int uid, VibrationAttributes attrs,
Vibration.Status status) {
switch (status) {
@@ -1395,6 +1420,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
void addService(String name, IBinder service) {
ServiceManager.addService(name, service);
}
+
+ VibratorControllerHolder createVibratorControllerHolder() {
+ return new VibratorControllerHolder();
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index fa8c35a6953e..19ea9f907306 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -626,6 +626,8 @@ final class AccessibilityController {
private boolean mForceShowMagnifiableBounds = false;
+ private final MagnificationSpec mMagnificationSpec = new MagnificationSpec();
+
DisplayMagnifier(WindowManagerService windowManagerService,
DisplayContent displayContent,
Display display,
@@ -655,13 +657,28 @@ final class AccessibilityController {
mAccessibilityTracing.logTrace(LOG_TAG + ".setMagnificationSpec",
FLAGS_MAGNIFICATION_CALLBACK, "spec={" + spec + "}");
}
- mMagnifedViewport.updateMagnificationSpec(spec);
+ updateMagnificationSpec(spec);
mMagnifedViewport.recomputeBounds();
mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec);
mService.scheduleAnimationLocked();
}
+ void updateMagnificationSpec(MagnificationSpec spec) {
+ if (spec != null) {
+ mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
+ } else {
+ mMagnificationSpec.clear();
+ }
+ // If this message is pending we are in a rotation animation and do not want
+ // to show the border. We will do so when the pending message is handled.
+ if (!mHandler.hasMessages(
+ MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
+ mMagnifedViewport.setMagnifiedRegionBorderShown(
+ isForceShowingMagnifiableBounds(), true);
+ }
+ }
+
void setForceShowMagnifiableBounds(boolean show) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".setForceShowMagnifiableBounds",
@@ -800,8 +817,7 @@ final class AccessibilityController {
case WindowManager.LayoutParams.TYPE_QS_DIALOG:
case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: {
Rect magnifiedRegionBounds = mTempRect2;
- mMagnifedViewport.getMagnifiedFrameInContentCoords(
- magnifiedRegionBounds);
+ getMagnifiedFrameInContentCoords(magnifiedRegionBounds);
Rect touchableRegionBounds = mTempRect1;
windowState.getTouchableRegion(mTempRegion1);
mTempRegion1.getBounds(touchableRegionBounds);
@@ -818,6 +834,14 @@ final class AccessibilityController {
}
}
+ void getMagnifiedFrameInContentCoords(Rect rect) {
+ Region magnificationRegion = new Region();
+ mMagnifedViewport.getMagnificationRegion(magnificationRegion);
+ magnificationRegion.getBounds(rect);
+ rect.offset((int) -mMagnificationSpec.offsetX, (int) -mMagnificationSpec.offsetY);
+ rect.scale(1.0f / mMagnificationSpec.scale);
+ }
+
void notifyImeWindowVisibilityChanged(boolean shown) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".notifyImeWindowVisibilityChanged",
@@ -832,13 +856,13 @@ final class AccessibilityController {
mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationSpecForWindow",
FLAGS_MAGNIFICATION_CALLBACK, "windowState={" + windowState + "}");
}
- MagnificationSpec spec = mMagnifedViewport.getMagnificationSpec();
- if (spec != null && !spec.isNop()) {
+
+ if (mMagnificationSpec != null && !mMagnificationSpec.isNop()) {
if (!windowState.shouldMagnify()) {
return null;
}
}
- return spec;
+ return mMagnificationSpec;
}
void getMagnificationRegion(Region outMagnificationRegion) {
@@ -852,6 +876,10 @@ final class AccessibilityController {
mMagnifedViewport.getMagnificationRegion(outMagnificationRegion);
}
+ boolean isMagnifying() {
+ return mMagnificationSpec.scale > 1.0f;
+ }
+
void destroy() {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".destroy", FLAGS_MAGNIFICATION_CALLBACK);
@@ -897,8 +925,6 @@ final class AccessibilityController {
private final Path mCircularPath;
- private final MagnificationSpec mMagnificationSpec = new MagnificationSpec();
-
private final float mBorderWidth;
private final int mHalfBorderWidth;
private final int mDrawBorderInset;
@@ -932,20 +958,6 @@ final class AccessibilityController {
outMagnificationRegion.set(mMagnificationRegion);
}
- void updateMagnificationSpec(MagnificationSpec spec) {
- if (spec != null) {
- mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
- } else {
- mMagnificationSpec.clear();
- }
- // If this message is pending we are in a rotation animation and do not want
- // to show the border. We will do so when the pending message is handled.
- if (!mHandler.hasMessages(
- MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
- setMagnifiedRegionBorderShown(isForceShowingMagnifiableBounds(), true);
- }
- }
-
void recomputeBounds() {
getDisplaySizeLocked(mScreenSize);
final int screenWidth = mScreenSize.x;
@@ -1127,21 +1139,6 @@ final class AccessibilityController {
}
}
- void getMagnifiedFrameInContentCoords(Rect rect) {
- MagnificationSpec spec = mMagnificationSpec;
- mMagnificationRegion.getBounds(rect);
- rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
- rect.scale(1.0f / spec.scale);
- }
-
- boolean isMagnifying() {
- return mMagnificationSpec.scale > 1.0f;
- }
-
- MagnificationSpec getMagnificationSpec() {
- return mMagnificationSpec;
- }
-
void drawWindowIfNeeded() {
recomputeBounds();
mWindow.postDrawIfNeeded();
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 2e0546eee8e7..1128d0c903cf 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1319,9 +1319,33 @@ class ActivityClientController extends IActivityClientController.Stub {
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
- if (r != null) {
+ if (r == null) return;
+ final TransitionController controller = r.mTransitionController;
+ if (!controller.isShellTransitionsEnabled()) {
+ r.setShowWhenLocked(showWhenLocked);
+ return;
+ }
+ if (controller.isCollecting()
+ && !mService.mKeyguardController.isKeyguardLocked(r.getDisplayId())) {
+ // Keyguard isn't locked, so this can be done as part of the collecting
+ // transition.
r.setShowWhenLocked(showWhenLocked);
+ return;
}
+ final Transition transition = new Transition(
+ showWhenLocked ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
+ 0 /* flags */, controller, mService.mWindowManager.mSyncEngine);
+ r.mTransitionController.startCollectOrQueue(transition, (deferred) -> {
+ transition.collect(r);
+ r.setShowWhenLocked(showWhenLocked);
+ if (transition.isNoOp()) {
+ transition.abort();
+ return;
+ }
+ controller.requestStartTransition(transition, null /* trigger */,
+ null /* remoteTransition */, null /* displayChange */);
+ transition.setReady(r, true);
+ });
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -1334,9 +1358,34 @@ class ActivityClientController extends IActivityClientController.Stub {
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
- if (r != null) {
+ if (r == null) return;
+ final TransitionController controller = r.mTransitionController;
+ // If shell transitions is not enabled just set it directly.
+ if (!controller.isShellTransitionsEnabled()) {
+ r.setInheritShowWhenLocked(inheritShowWhenLocked);
+ return;
+ }
+ if (controller.isCollecting()
+ && !mService.mKeyguardController.isKeyguardLocked(r.getDisplayId())) {
+ // Keyguard isn't locked, so this can be done as part of the collecting
+ // transition.
r.setInheritShowWhenLocked(inheritShowWhenLocked);
+ return;
}
+ final Transition transition = new Transition(
+ inheritShowWhenLocked ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
+ 0 /* flags */, controller, mService.mWindowManager.mSyncEngine);
+ r.mTransitionController.startCollectOrQueue(transition, (deferred) -> {
+ transition.collect(r);
+ r.setInheritShowWhenLocked(inheritShowWhenLocked);
+ if (transition.isNoOp()) {
+ transition.abort();
+ return;
+ }
+ controller.requestStartTransition(transition, null /* trigger */,
+ null /* remoteTransition */, null /* displayChange */);
+ transition.setReady(r, true);
+ });
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d6f52b89819e..85580ac6810a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1034,20 +1034,19 @@ class ActivityStarter {
}
if (err == ActivityManager.START_SUCCESS && aInfo == null) {
+ // We couldn't find the specific class specified in the Intent.
+ err = ActivityManager.START_CLASS_NOT_FOUND;
+
if (isArchivingEnabled()) {
PackageArchiver packageArchiver = mService
.getPackageManagerInternalLocked()
.getPackageArchiver();
if (packageArchiver.isIntentResolvedToArchivedApp(intent, mRequest.userId)) {
- return packageArchiver
+ err = packageArchiver
.requestUnarchiveOnActivityStart(
intent, callingPackage, mRequest.userId, realCallingUid);
}
}
-
- // We couldn't find the specific class specified in the Intent.
- // Also the end of the line.
- err = ActivityManager.START_CLASS_NOT_FOUND;
}
if (err == ActivityManager.START_SUCCESS && sourceRecord != null
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 82dbf8d4242b..716aee3f8692 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1429,14 +1429,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return mTokenMap.get(binder);
}
- ActivityRecord getActivityRecord(IBinder binder) {
- final WindowToken token = getWindowToken(binder);
- if (token == null) {
- return null;
- }
- return token.asActivityRecord();
- }
-
void addWindowToken(IBinder binder, WindowToken token) {
final DisplayContent dc = mWmService.mRoot.getWindowTokenDisplay(token);
if (dc != null) {
@@ -2250,7 +2242,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
- mWmService.mDisplayManagerInternal.performTraversal(transaction);
if (shellTransitions) {
// Before setDisplayProjection is applied by the start transaction of transition,
// set the transform hint to avoid using surface in old rotation.
@@ -6985,7 +6976,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
- final ActivityRecord r = getActivityRecord(token);
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
// Ignore the animating recents so the fixed rotation transform won't be switched twice
// by finishing the recents animation and moving it to top. That also avoids flickering
// due to wait for previous activity to be paused if it supports PiP that ignores the
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 7b23004ae952..7a3124d2fa83 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -160,7 +160,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
// Invisible activity should be stopped. If the recents activity is alive and its doesn't
// need to relaunch by current configuration, then it may be already in stopped state.
- if (!targetActivity.isState(STOPPING, STOPPED)) {
+ if (!targetActivity.finishing && targetActivity.isAttached()
+ && !targetActivity.isState(STOPPING, STOPPED)) {
// Add to stopping instead of stop immediately. So the client has the chance to perform
// traversal in non-stopped state (ViewRootImpl.mStopped) that would initialize more
// things (e.g. the measure can be done earlier). The actual stop will be performed when
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 587cc7489763..25646f19c461 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -249,6 +249,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
/** Reference to default display so we can quickly look it up. */
private DisplayContent mDefaultDisplay;
private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
+ private final SparseArray<SurfaceControl.Transaction> mDisplayTransactions =
+ new SparseArray<>();
/** The current user */
int mCurrentUser;
@@ -569,22 +571,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}, true /* traverseTopToBottom */);
}
- /**
- * Returns the app window token for the input binder if it exist in the system.
- * NOTE: Only one AppWindowToken is allowed to exist in the system for a binder token, since
- * AppWindowToken represents an activity which can only exist on one display.
- */
- ActivityRecord getActivityRecord(IBinder binder) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final DisplayContent dc = mChildren.get(i);
- final ActivityRecord activity = dc.getActivityRecord(binder);
- if (activity != null) {
- return activity;
- }
- }
- return null;
- }
-
/** Returns the window token for the input binder if it exist in the system. */
WindowToken getWindowToken(IBinder binder) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
@@ -991,11 +977,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
for (int j = 0; j < count; ++j) {
final DisplayContent dc = mChildren.get(j);
dc.applySurfaceChangesTransaction();
+ mDisplayTransactions.append(dc.mDisplayId, dc.getSyncTransaction());
}
// Give the display manager a chance to adjust properties like display rotation if it needs
// to.
- mWmService.mDisplayManagerInternal.performTraversal(t);
+ mWmService.mDisplayManagerInternal.performTraversal(t, mDisplayTransactions);
+ mDisplayTransactions.clear();
}
/**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8f9ed8353456..5b5177683252 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5070,7 +5070,7 @@ class Task extends TaskFragment {
mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
if (waitingActivity != null) {
- mWmService.setWindowOpaqueLocked(waitingActivity.token, false);
+ waitingActivity.setMainWindowOpaque(false);
if (waitingActivity.attachedToProcess()) {
try {
waitingActivity.app.getThread().scheduleTranslucentConversionComplete(
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2accf9a2a43a..1e58306ef8d8 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2917,6 +2917,26 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie);
}
+ /**
+ * Get whether the transition, in its current state, is a no-op. This should be avoided. It is
+ * only here for legacy usages where we can't tell ahead-of-time whether something will
+ * generate a change.
+ */
+ boolean isNoOp() {
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ // This is the same criteria as the rejection logic in calculateTargets
+ final WindowContainer<?> wc = mParticipants.valueAt(i);
+ if (!wc.isAttached()) continue;
+ // The level of transition target should be at least window token.
+ if (wc.asWindowState() != null) continue;
+ final ChangeInfo changeInfo = mChanges.get(wc);
+ // Reject no-ops
+ if (!changeInfo.hasChanged()) continue;
+ return false;
+ }
+ return true;
+ }
+
@VisibleForTesting
static class ChangeInfo {
private static final int FLAG_NONE = 0;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 426694d178af..fc23700d0796 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1073,7 +1073,7 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
- final ActivityRecord atoken = mRoot.getActivityRecord(token);
+ final ActivityRecord atoken = ActivityRecord.forTokenLocked(token);
if (atoken == null) {
return;
}
@@ -3105,13 +3105,6 @@ public class WindowManagerService extends IWindowManager.Stub
return mRecentsAnimationController != null && mRecentsAnimationController.isTargetApp(r);
}
- void setWindowOpaqueLocked(IBinder token, boolean isOpaque) {
- final ActivityRecord wtoken = mRoot.getActivityRecord(token);
- if (wtoken != null) {
- wtoken.setMainWindowOpaque(isOpaque);
- }
- }
-
boolean isValidPictureInPictureAspectRatio(DisplayContent displayContent, float aspectRatio) {
return displayContent.getPinnedTaskController().isValidPictureInPictureAspectRatio(
aspectRatio);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3f889c01bafb..8cd399f6a12e 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1471,12 +1471,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final int index = task.mChildren.indexOf(topTaskFragment);
task.mChildren.remove(taskFragment);
task.mChildren.add(index, taskFragment);
- if (taskFragment.hasChild()) {
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
- } else {
+ if (!taskFragment.hasChild()) {
// Ensure that the child layers are updated if the TaskFragment is empty
task.assignChildLayers();
}
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
}
break;
@@ -1491,12 +1490,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (task != null) {
task.mChildren.remove(taskFragment);
task.mChildren.add(0, taskFragment);
- if (taskFragment.hasChild()) {
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
- } else {
+ if (!taskFragment.hasChild()) {
// Ensure that the child layers are updated if the TaskFragment is empty.
task.assignChildLayers();
}
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
break;
}
@@ -1505,12 +1503,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (task != null) {
task.mChildren.remove(taskFragment);
task.mChildren.add(taskFragment);
- if (taskFragment.hasChild()) {
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
- } else {
+ if (!taskFragment.hasChild()) {
// Ensure that the child layers are updated if the TaskFragment is empty.
task.assignChildLayers();
}
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
break;
}
diff --git a/services/incremental/OWNERS b/services/incremental/OWNERS
index 7ebb962c8feb..c18a9e5b90c3 100644
--- a/services/incremental/OWNERS
+++ b/services/incremental/OWNERS
@@ -1,8 +1,4 @@
# Bug component: 554432
-include /services/core/java/com/android/server/pm/OWNERS
+include /PACKAGE_MANAGER_OWNERS
-alexbuy@google.com
-schfan@google.com
-toddke@google.com
-zyy@google.com
-patb@google.com
+zyy@google.com \ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index 8f464d41792d..fc2eb2652d2d 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -17,6 +17,7 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
+import android.companion.virtual.VirtualDeviceManager
import android.os.Handler
import android.os.UserHandle
import android.util.ArrayMap
@@ -213,7 +214,10 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS
val uid = key.first
val appOpCode = key.second
- listener.onUidModeChanged(uid, appOpCode, mode)
+ listener.onUidModeChanged(uid,
+ appOpCode,
+ mode,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)
}
}
diff --git a/services/profcollect/OWNERS b/services/profcollect/OWNERS
index b380e39529e3..be9e61fa298b 100644
--- a/services/profcollect/OWNERS
+++ b/services/profcollect/OWNERS
@@ -1,3 +1 @@
-srhines@google.com
-yabinc@google.com
-yikong@google.com
+include platform/prebuilts/clang/host/linux-x86:/OWNERS
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 582b712ec3fc..fb0fbe87dab3 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -62,7 +62,7 @@ public final class ProfcollectForwardingService extends SystemService {
private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
private static final String INTENT_UPLOAD_PROFILES =
"com.android.server.profcollect.UPLOAD_PROFILES";
- private static final long BG_PROCESS_PERIOD = TimeUnit.HOURS.toMillis(4); // every 4 hours.
+ private static final long BG_PROCESS_INTERVAL = TimeUnit.HOURS.toMillis(4); // every 4 hours.
private IProfCollectd mIProfcollect;
private static ProfcollectForwardingService sSelfService;
@@ -226,7 +226,7 @@ public final class ProfcollectForwardingService extends SystemService {
js.schedule(new JobInfo.Builder(JOB_IDLE_PROCESS, JOB_SERVICE_NAME)
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
- .setPeriodic(BG_PROCESS_PERIOD)
+ .setPeriodic(BG_PROCESS_INTERVAL)
.setPriority(JobInfo.PRIORITY_MIN)
.build());
}
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 56423b961813..897afbfd2046 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -73,7 +73,6 @@ android_ravenwood_test {
"androidx.annotation_annotation",
"androidx.test.rules",
"framework",
- "mockito_ravenwood",
"ravenwood-runtime",
"ravenwood-utils",
"services",
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 75febd902dcf..42bcb3367933 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -103,6 +103,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.SparseArray;
import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayAdjustments;
@@ -432,7 +433,7 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -507,7 +508,7 @@ public class DisplayManagerServiceTest {
assertTrue(expectedDisplayTypeToViewPortTypeMapping.keySet().contains(info.type));
}
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -559,7 +560,7 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -594,7 +595,7 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -632,7 +633,7 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -667,7 +668,7 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -947,7 +948,7 @@ public class DisplayManagerServiceTest {
PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1439,7 +1440,7 @@ public class DisplayManagerServiceTest {
PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1694,7 +1695,7 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1728,7 +1729,7 @@ public class DisplayManagerServiceTest {
null /* projection */, PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
assertNotNull(ddi);
@@ -1797,7 +1798,7 @@ public class DisplayManagerServiceTest {
mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
assertNotNull(ddi);
@@ -2912,6 +2913,11 @@ public class DisplayManagerServiceTest {
assertEquals(expectedMode, displayInfo.getMode());
}
+ private void performTraversalInternal(DisplayManagerService displayManager) {
+ displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class),
+ new SparseArray<>());
+ }
+
private int getDisplayIdForDisplayDevice(
DisplayManagerService displayManager,
DisplayManagerService.BinderService displayManagerBinderService,
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 321d945e9c15..d9283063b7fc 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -46,7 +46,6 @@ android_test {
"androidx.test.espresso.core",
"androidx.test.espresso.contrib",
"androidx.test.ext.truth",
- "backup_flags_lib",
"flag-junit",
"frameworks-base-testutils",
"hamcrest-library",
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index 47ae97fc5d27..0e85626ab678 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -44,6 +44,7 @@ import android.app.AppOpsManager.PackageOps;
import android.app.IActivityManager;
import android.app.IUidObserver;
import android.app.usage.UsageStatsManager;
+import android.companion.virtual.VirtualDeviceManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -309,7 +310,8 @@ public class AppStateTrackerTest {
mRestrictedPackages.remove(p);
}
if (mAppOpsCallback != null) {
- mAppOpsCallback.opChanged(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName);
+ mAppOpsCallback.opChanged(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index b39cd04f5be2..a4761555384e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -125,6 +125,7 @@ import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.app.tare.EconomyManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.companion.virtual.VirtualDeviceManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -2980,7 +2981,8 @@ public final class AlarmManagerServiceTest {
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
- mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+ mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
assertAndHandleMessageSync(REMOVE_EXACT_ALARMS);
verify(mService).removeExactAlarmsOnPermissionRevoked(TEST_CALLING_UID,
TEST_CALLING_PACKAGE, true);
@@ -2993,7 +2995,8 @@ public final class AlarmManagerServiceTest {
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
- mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+ mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
verify(mService.mHandler, never()).sendMessageAtTime(
argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong());
@@ -3008,7 +3011,8 @@ public final class AlarmManagerServiceTest {
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
- mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+ mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index f49f6383b3c8..64fef68e387a 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -81,7 +81,6 @@ android_ravenwood_test {
"androidx.annotation_annotation",
"androidx.test.rules",
"truth",
- "mockito_ravenwood",
],
srcs: [
":power_stats_ravenwood_tests",
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index e22d99d45521..ad6e2c657c96 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -84,6 +84,7 @@ android_test {
"flag-junit",
"ravenwood-junit",
"net_flags_lib",
+ "CtsVirtualDeviceCommonLib",
],
libs: [
@@ -152,7 +153,6 @@ android_ravenwood_test {
static_libs: [
"androidx.annotation_annotation",
"androidx.test.rules",
- "mockito_ravenwood",
"services.core",
],
srcs: [
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
index a2aaccc679dc..b487dc69ec6e 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
@@ -16,6 +16,8 @@
package com.android.server.appop;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
@@ -31,17 +33,23 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpActiveChangedListener;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
+import android.virtualdevice.cts.common.FakeAssociationRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Tests app ops version upgrades
@@ -50,6 +58,8 @@ import java.util.List;
@RunWith(AndroidJUnit4.class)
public class AppOpsActiveWatcherTest {
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -69,7 +79,7 @@ public class AppOpsActiveWatcherTest {
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
- isNull(), eq(true), anyInt(), anyInt());
+ isNull(), eq(Context.DEVICE_ID_DEFAULT), eq(true), anyInt(), anyInt());
// This should be the only callback we got
verifyNoMoreInteractions(listener);
@@ -88,7 +98,7 @@ public class AppOpsActiveWatcherTest {
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()), isNull(),
- eq(false), anyInt(), anyInt());
+ eq(Context.DEVICE_ID_DEFAULT), eq(false), anyInt(), anyInt());
// Verify that the op is not active
assertThat(appOpsManager.isOperationActive(AppOpsManager.OP_CAMERA,
@@ -126,7 +136,7 @@ public class AppOpsActiveWatcherTest {
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()), isNull(),
- eq(true), anyInt(), anyInt());
+ eq(Context.DEVICE_ID_DEFAULT), eq(true), anyInt(), anyInt());
// Finish up
appOpsManager.finishOp(AppOpsManager.OP_CAMERA);
@@ -134,6 +144,64 @@ public class AppOpsActiveWatcherTest {
}
@Test
+ public void testWatchActiveOpsForExternalDevice() {
+ final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
+ VirtualDeviceManager.class);
+ AtomicInteger virtualDeviceId = new AtomicInteger();
+ runWithShellPermissionIdentity(() -> {
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder().setName("virtual_device").build());
+ virtualDeviceId.set(virtualDevice.getDeviceId());
+ });
+ final OnOpActiveChangedListener listener = mock(OnOpActiveChangedListener.class);
+ AttributionSource attributionSource = new AttributionSource(Process.myUid(),
+ getContext().getOpPackageName(), getContext().getAttributionTag(),
+ virtualDeviceId.get());
+
+ final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+ appOpsManager.startWatchingActive(new String[]{AppOpsManager.OPSTR_CAMERA,
+ AppOpsManager.OPSTR_RECORD_AUDIO}, getContext().getMainExecutor(), listener);
+
+ appOpsManager.startOpNoThrow(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_CAMERA, attributionSource, false, "",
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+
+ verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
+ .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
+ eq(Process.myUid()), eq(getContext().getOpPackageName()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()), eq(true),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_CAMERA, attributionSource);
+
+ verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
+ .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
+ eq(Process.myUid()), eq(getContext().getOpPackageName()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()), eq(false),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.stopWatchingActive(listener);
+
+ appOpsManager.startOpNoThrow(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_CAMERA, attributionSource, false, "",
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_CAMERA, attributionSource);
+
+ verifyNoMoreInteractions(listener);
+ }
+
+ @Test
public void testIsRunning() throws Exception {
final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
// Start the op
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
index b5229d81bdc8..1abd4eb6157f 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
@@ -22,27 +22,36 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpNotedListener;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
+import android.virtualdevice.cts.common.FakeAssociationRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
* Tests watching noted ops.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AppOpsNotedWatcherTest {
-
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -66,12 +75,14 @@ public class AppOpsNotedWatcherTest {
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED));
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpNoted(eq(AppOpsManager.OPSTR_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED));
// Stop watching
@@ -96,13 +107,54 @@ public class AppOpsNotedWatcherTest {
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(2)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED));
// Finish up
appOpsManager.stopWatchingNoted(listener);
}
+ @Test
+ public void testWatchNotedOpsForExternalDevice() {
+ final AppOpsManager.OnOpNotedListener listener = mock(
+ AppOpsManager.OnOpNotedListener.class);
+ final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
+ VirtualDeviceManager.class);
+ AtomicInteger virtualDeviceId = new AtomicInteger();
+ runWithShellPermissionIdentity(() -> {
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder().setName("virtual_device").build());
+ virtualDeviceId.set(virtualDevice.getDeviceId());
+ });
+ AttributionSource attributionSource = new AttributionSource(Process.myUid(),
+ getContext().getOpPackageName(), getContext().getAttributionTag(),
+ virtualDeviceId.get());
+
+ final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+ appOpsManager.startWatchingNoted(new int[]{AppOpsManager.OP_FINE_LOCATION,
+ AppOpsManager.OP_CAMERA}, listener);
+
+ appOpsManager.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, attributionSource, "message");
+
+ verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
+ .times(1)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
+ eq(Process.myUid()), eq(getContext().getOpPackageName()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()),
+ eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED));
+
+ appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_FINE_LOCATION, attributionSource);
+
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.stopWatchingNoted(listener);
+
+ verifyNoMoreInteractions(listener);
+ }
+
private static Context getContext() {
return InstrumentationRegistry.getContext();
}
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
index e98a4dded99b..2890078f7638 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
@@ -16,6 +16,8 @@
package com.android.server.appop;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -25,22 +27,31 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpStartedListener;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
+import android.virtualdevice.cts.common.FakeAssociationRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
+import java.util.concurrent.atomic.AtomicInteger;
+
/** Tests watching started ops. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AppOpsStartedWatcherTest {
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -63,15 +74,17 @@ public class AppOpsStartedWatcherTest {
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED),
+ eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED),
+ eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
@@ -97,8 +110,9 @@ public class AppOpsStartedWatcherTest {
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(2)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
- eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(getContext().getAttributionTag()), eq(Context.DEVICE_ID_DEFAULT),
+ eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED),
+ eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
verifyNoMoreInteractions(listener);
@@ -109,6 +123,50 @@ public class AppOpsStartedWatcherTest {
appOpsManager.stopWatchingStarted(listener);
}
+ @Test
+ public void testWatchStartedOpsForExternalDevice() {
+ final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
+ VirtualDeviceManager.class);
+ AtomicInteger virtualDeviceId = new AtomicInteger();
+ runWithShellPermissionIdentity(() -> {
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder().setName("virtual_device").build());
+ virtualDeviceId.set(virtualDevice.getDeviceId());
+ });
+ final OnOpStartedListener listener = mock(OnOpStartedListener.class);
+ AttributionSource attributionSource = new AttributionSource(Process.myUid(),
+ getContext().getOpPackageName(), getContext().getAttributionTag(),
+ virtualDeviceId.get());
+
+ final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+ appOpsManager.startWatchingStarted(new int[]{AppOpsManager.OP_FINE_LOCATION,
+ AppOpsManager.OP_CAMERA}, listener);
+
+ appOpsManager.startOpNoThrow(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_FINE_LOCATION, attributionSource, false,
+ "message", 0, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+
+ verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
+ .times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
+ eq(Process.myUid()), eq(getContext().getOpPackageName()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()),
+ eq(AppOpsManager.OP_FLAG_SELF),
+ eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
+
+ appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
+ AppOpsManager.OP_FINE_LOCATION, attributionSource);
+
+ verifyNoMoreInteractions(listener);
+
+ appOpsManager.stopWatchingStarted(listener);
+
+ verifyNoMoreInteractions(listener);
+ }
+
private static Context getContext() {
return InstrumentationRegistry.getContext();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
index 3a9c0f0f1790..a1f0dbd4e889 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
@@ -70,7 +70,7 @@ public class BaseClientMonitorTest {
mClientMonitor.binderDied();
assertThat(mClientMonitor.mCanceled).isTrue();
- assertThat(mClientMonitor.getListener()).isNull();
+ assertThat(mClientMonitor.getListener()).isNotNull();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
index c8bfaa90d863..5b81277250c2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
@@ -87,14 +87,6 @@ public class FaceGenerateChallengeClientTest {
verify(mCallback).onClientFinished(mClient, true);
}
- @Test
- public void generateChallenge_nullListener() {
- createClient(null);
- mClient.start(mCallback);
-
- verify(mCallback).onClientFinished(mClient, false);
- }
-
private void createClient(ClientMonitorCallbackConverter listener) {
mClient = new FaceGenerateChallengeClient(mContext, () -> mAidlSession, mToken, listener,
USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
index 07dd59d2e2d8..a4628ee3b52b 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
@@ -18,6 +18,8 @@ package com.android.server.companion.virtual;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.content.Context.DEVICE_ID_INVALID;
@@ -135,4 +137,34 @@ public class VirtualDeviceTest {
when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_SENSORS)).thenReturn(DEVICE_POLICY_CUSTOM);
assertThat(virtualDevice.hasCustomSensorSupport()).isTrue();
}
+
+ @Test
+ public void virtualDevice_hasCustomAudioInputSupport() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
+
+ VirtualDevice virtualDevice =
+ new VirtualDevice(
+ mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null);
+
+ when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO)).thenReturn(DEVICE_POLICY_DEFAULT);
+ assertThat(virtualDevice.hasCustomAudioInputSupport()).isFalse();
+
+ when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO)).thenReturn(DEVICE_POLICY_CUSTOM);
+ assertThat(virtualDevice.hasCustomAudioInputSupport()).isTrue();
+ }
+
+ @Test
+ public void virtualDevice_hasCustomCameraSupport() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
+
+ VirtualDevice virtualDevice =
+ new VirtualDevice(
+ mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null);
+
+ when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_CAMERA)).thenReturn(DEVICE_POLICY_DEFAULT);
+ assertThat(virtualDevice.hasCustomCameraSupport()).isFalse();
+
+ when(mVirtualDevice.getDevicePolicy(POLICY_TYPE_CAMERA)).thenReturn(DEVICE_POLICY_CUSTOM);
+ assertThat(virtualDevice.hasCustomCameraSupport()).isTrue();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 3de167e72ba0..dacff4c07759 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -23,7 +23,6 @@ import static org.junit.Assert.fail;
import android.content.Context;
import android.graphics.FontListParser;
-import android.graphics.fonts.FontFamily;
import android.graphics.fonts.FontManager;
import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontUpdateRequest;
@@ -324,15 +323,16 @@ public final class UpdatableFontDirTest {
Function<Map<String, File>, FontConfig> configSupplier = (map) -> {
FontConfig.Font fooFont = new FontConfig.Font(
new File(mPreinstalledFontDirs.get(0), "foo.ttf"), null, "foo",
- new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null);
+ new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE);
FontConfig.Font barFont = new FontConfig.Font(
new File(mPreinstalledFontDirs.get(1), "bar.ttf"), null, "bar",
- new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null);
+ new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null,
+ FontConfig.Font.VAR_TYPE_AXES_NONE);
FontConfig.FontFamily family = new FontConfig.FontFamily(
Arrays.asList(fooFont, barFont), null,
- FontConfig.FontFamily.VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ FontConfig.FontFamily.VARIANT_DEFAULT);
return new FontConfig(Collections.emptyList(),
Collections.emptyList(),
Collections.singletonList(new FontConfig.NamedFamilyList(
@@ -492,10 +492,9 @@ public final class UpdatableFontDirTest {
mConfigFile, mCurrentTimeSupplier, (map) -> {
FontConfig.Font font = new FontConfig.Font(
file, null, "bar", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT),
- 0, null, null);
+ 0, null, null, FontConfig.Font.VAR_TYPE_AXES_NONE);
FontConfig.FontFamily family = new FontConfig.FontFamily(
- Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
return new FontConfig(
Collections.emptyList(),
Collections.emptyList(),
@@ -647,10 +646,9 @@ public final class UpdatableFontDirTest {
mConfigFile, mCurrentTimeSupplier, (map) -> {
FontConfig.Font font = new FontConfig.Font(
file, null, "test", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null,
- null);
+ null, FontConfig.Font.VAR_TYPE_AXES_NONE);
FontConfig.FontFamily family = new FontConfig.FontFamily(
- Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT,
- FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+ Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
return new FontConfig(Collections.emptyList(), Collections.emptyList(),
Collections.singletonList(new FontConfig.NamedFamilyList(
Collections.singletonList(family), "sans-serif")),
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 1172a8744da7..46418026e540 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -19,6 +19,7 @@ package com.android.server.hdmi;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
import static com.android.server.hdmi.OneTouchPlayAction.STATE_WAITING_FOR_REPORT_POWER_STATUS;
import static com.google.common.truth.Truth.assertThat;
@@ -46,6 +47,7 @@ import org.junit.runners.JUnit4;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.concurrent.TimeUnit;
/** Tests for {@link OneTouchPlayAction} */
@SmallTest
@@ -70,6 +72,7 @@ public class OneTouchPlayActionTest {
private Context mContextSpy;
private HdmiControlService mHdmiControlService;
+ private HdmiCecController mHdmiCecController;
private FakeNativeWrapper mNativeWrapper;
private FakePowerManagerWrapper mPowerManager;
private FakeHdmiCecConfig mHdmiCecConfig;
@@ -108,10 +111,10 @@ public class OneTouchPlayActionTest {
mHdmiControlService.setHdmiCecConfig(mHdmiCecConfig);
setHdmiControlEnabled(hdmiControlEnabled);
mNativeWrapper = new FakeNativeWrapper();
- HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
- mHdmiControlService.setCecController(hdmiCecController);
+ mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
@@ -618,6 +621,53 @@ public class OneTouchPlayActionTest {
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
+ @Test
+ public void onWakeUp_notInteractive_startOneTouchPlay() throws Exception {
+ setUp(true);
+
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mPowerManager.setInteractive(false);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPowerManager.isInteractive()).isFalse();
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn =
+ HdmiCecMessageBuilder.buildTextViewOn(
+ mHdmiControlService.playback().getDeviceInfo().getLogicalAddress(),
+ ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ }
+
+
+ @Test
+ public void onWakeUp_interruptedByOnStandby_notInteractive_OneTouchPlayNotStarted()
+ throws Exception {
+ setUp(true);
+ long allocationDelay = TimeUnit.SECONDS.toMillis(1);
+ mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ mPowerManager.setInteractive(false);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPowerManager.isInteractive()).isFalse();
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn =
+ HdmiCecMessageBuilder.buildTextViewOn(
+ mHdmiControlService.playback().getDeviceInfo().getLogicalAddress(),
+ ADDR_TV);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn);
+
+ }
+
private static class TestActionTimer implements ActionTimer {
private int mState;
diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
index 398148ff4d3b..2e5feffd8673 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
@@ -273,7 +273,7 @@ public class SuspendPackagesTest {
final CountDownLatch latch = new CountDownLatch(1);
final IAppOpsCallback watcher = new IAppOpsCallback.Stub() {
@Override
- public void opChanged(int op, int uid, String packageName) {
+ public void opChanged(int op, int uid, String packageName, String persistentDeviceId) {
if (op == code && packageName.equals(TEST_APP_PACKAGE_NAME)) {
latch.countDown();
}
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 9c2cba8ecf96..ef0ac33bcfc5 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -227,8 +227,6 @@ import android.permission.PermissionManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.platform.test.rule.DeniedDevices;
-import android.platform.test.rule.DeviceProduct;
import android.platform.test.rule.LimitDevicesRule;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
@@ -336,7 +334,6 @@ import java.util.function.Consumer;
@RunWith(AndroidTestingRunner.class)
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@RunWithLooper
-@DeniedDevices(denied = {DeviceProduct.CF_AUTO})
public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
private static final String TEST_PACKAGE = "The.name.is.Package.Test.Package";
@@ -593,7 +590,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt()))
.thenReturn(INVALID_TASK_ID);
mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
- when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0});
+ when(mUm.getProfileIds(eq(mUserId), eq(false))).thenReturn(new int[] { mUserId });
when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(true);
@@ -881,9 +878,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private void simulatePackageRemovedBroadcast(String pkg, int uid) {
// mimics receive broadcast that package is removed, but doesn't remove the package.
final Bundle extras = new Bundle();
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
- new String[]{pkg});
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, new int[]{uid});
+ extras.putInt(Intent.EXTRA_UID, uid);
final Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
intent.setData(Uri.parse("package:" + pkg));
@@ -1031,7 +1026,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private NotificationRecord generateNotificationRecord(NotificationChannel channel,
long postTime) {
- final StatusBarNotification sbn = generateSbn(PKG, mUid, postTime, 0);
+ final StatusBarNotification sbn = generateSbn(PKG, mUid, postTime, mUserId);
return new NotificationRecord(mContext, sbn, channel);
}
@@ -1766,7 +1761,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotification_appBlocked", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
verify(mWorkerHandler, never()).post(
any(NotificationManagerService.EnqueueNotificationRunnable.class));
@@ -1776,7 +1771,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
assertEquals(1, notifs.length);
@@ -1787,7 +1782,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testEnqueueNotificationWithTag_WritesExpectedLogs() throws Exception {
final String tag = "testEnqueueNotificationWithTag_WritesExpectedLog";
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
assertEquals(1, mNotificationRecordLogger.numCalls());
@@ -1828,12 +1823,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Notification original = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon).build();
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, original, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, original, mUserId);
Notification update = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setCategory(Notification.CATEGORY_ALARM).build();
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, update, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, update, mUserId);
waitForIdle();
assertEquals(2, mNotificationRecordLogger.numCalls());
@@ -1853,9 +1848,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate() throws Exception {
final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate";
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
assertEquals(2, mNotificationRecordLogger.numCalls());
assertTrue(mNotificationRecordLogger.get(0).wasLogged);
@@ -1869,10 +1864,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnTitleUpdate";
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
generateNotificationRecord(null).getNotification(),
- 0);
+ mUserId);
final Notification notif = generateNotificationRecord(null).getNotification();
notif.extras.putString(Notification.EXTRA_TITLE, "Changed title");
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notif, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notif, mUserId);
waitForIdle();
assertEquals(2, mNotificationRecordLogger.numCalls());
assertEquals(NOTIFICATION_POSTED, mNotificationRecordLogger.event(0));
@@ -1885,11 +1880,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Notification notification = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon).build();
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, mUserId);
waitForIdle();
- mBinderService.cancelNotificationWithTag(PKG, PKG, tag, 0, 0);
+ mBinderService.cancelNotificationWithTag(PKG, PKG, tag, 0, mUserId);
waitForIdle();
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, mUserId);
waitForIdle();
assertEquals(3, mNotificationRecordLogger.numCalls());
@@ -1949,7 +1944,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.build();
n.actions[1] = null;
- mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
waitForIdle();
StatusBarNotification[] posted = mBinderService.getActiveNotifications(PKG);
@@ -1970,7 +1965,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
n.actions[0] = null;
n.actions[1] = null;
- mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
waitForIdle();
StatusBarNotification[] posted = mBinderService.getActiveNotifications(PKG);
@@ -1982,7 +1977,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void enqueueNotificationWithTag_usesAndFinishesTracker() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
assertThat(mPostNotificationTrackerFactory.mCreatedTrackers).hasSize(1);
assertThat(mPostNotificationTrackerFactory.mCreatedTrackers.get(0).isOngoing()).isTrue();
@@ -2000,7 +1995,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertThrows(Exception.class,
() -> mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- /* notification= */ null, 0));
+ /* notification= */ null, mUserId));
waitForIdle();
@@ -2017,7 +2012,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
assertThat(mBinderService.getActiveNotifications(PKG)).hasLength(0);
@@ -2032,7 +2027,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
assertThat(mBinderService.getActiveNotifications(PKG)).hasLength(0);
@@ -2044,7 +2039,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void enqueueNotification_acquiresAndReleasesWakeLock() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_acquiresAndReleasesWakeLock", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
assertThat(mAcquiredWakeLocks).hasSize(1);
@@ -2062,7 +2057,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertThrows(Exception.class,
() -> mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_throws_acquiresAndReleasesWakeLock", 0,
- /* notification= */ null, 0));
+ /* notification= */ null, mUserId));
verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
assertThat(mAcquiredWakeLocks).hasSize(1);
@@ -2077,7 +2072,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
assertThat(mAcquiredWakeLocks).hasSize(1);
@@ -2098,7 +2093,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_notPosted_acquiresAndReleasesWakeLock", 0,
- notif, 0);
+ notif, mUserId);
verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
assertThat(mAcquiredWakeLocks).hasSize(1);
@@ -2123,7 +2118,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"enqueueNotification_setsWakeLockWorkSource", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
InOrder inOrder = inOrder(mPowerManager, wakeLock);
@@ -2137,7 +2132,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelNonexistentNotification() throws Exception {
mBinderService.cancelNotificationWithTag(PKG, PKG,
- "testCancelNonexistentNotification", 0, 0);
+ "testCancelNonexistentNotification", 0, mUserId);
waitForIdle();
// The notification record logger doesn't even get called when a nonexistent notification
// is cancelled, because that happens very frequently and is not interesting.
@@ -2148,9 +2143,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testCancelNotificationImmediatelyAfterEnqueue", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
mBinderService.cancelNotificationWithTag(PKG, PKG,
- "testCancelNotificationImmediatelyAfterEnqueue", 0, 0);
+ "testCancelNotificationImmediatelyAfterEnqueue", 0, mUserId);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(PKG);
@@ -2185,13 +2180,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testCancelNotificationWhilePostedAndEnqueued", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
waitForIdle();
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testCancelNotificationWhilePostedAndEnqueued", 0,
- generateNotificationRecord(null).getNotification(), 0);
+ generateNotificationRecord(null).getNotification(), mUserId);
mBinderService.cancelNotificationWithTag(PKG, PKG,
- "testCancelNotificationWhilePostedAndEnqueued", 0, 0);
+ "testCancelNotificationWhilePostedAndEnqueued", 0, mUserId);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(PKG);
@@ -3406,12 +3401,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testPostNotification_appPermissionFixed() throws Exception {
when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
- when(mPermissionHelper.isPermissionFixed(PKG, 0)).thenReturn(true);
+ when(mPermissionHelper.isPermissionFixed(PKG, mUserId)).thenReturn(true);
NotificationRecord temp = generateNotificationRecord(mTestNotificationChannel);
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testPostNotification_appPermissionFixed", 0,
- temp.getNotification(), 0);
+ temp.getNotification(), mUserId);
waitForIdle();
assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
StatusBarNotification[] notifs =
@@ -3443,7 +3438,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
- generateNotificationRecord(null, tv).getNotification(), 0);
+ generateNotificationRecord(null, tv).getNotification(), mUserId);
verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
anyString(), anyInt(), eq("foo"), eq(null), anyBoolean(), anyBoolean());
}
@@ -3458,7 +3453,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv",
- 0, generateNotificationRecord(null, tv).getNotification(), 0);
+ 0, generateNotificationRecord(null, tv).getNotification(), mUserId);
verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
anyBoolean(), anyBoolean());
@@ -11859,10 +11854,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testGetActiveNotification_filtersUsers() throws Exception {
- when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0, 10});
+ when(mUm.getProfileIds(mUserId, false)).thenReturn(new int[]{mUserId, 10});
NotificationRecord nr0 =
- generateNotificationRecord(mTestNotificationChannel, 0);
+ generateNotificationRecord(mTestNotificationChannel, mUserId);
mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag0",
nr0.getSbn().getId(), nr0.getSbn().getNotification(), nr0.getSbn().getUserId());
@@ -12316,7 +12311,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setFullScreenIntent(mock(PendingIntent.class), true)
.build();
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
+ mService.fixNotification(n, PKG, "tag", 9, mUserId, mUid, NOT_FOREGROUND_SERVICE, true);
final int stickyFlag = n.flags & Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 6df6296fa9ec..74622014aa5c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -2123,6 +2123,25 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testDefaultRulesFromConfig_modesApi_getPolicies() {
+ // After mZenModeHelper was created, set some things in the policy so it's changed from
+ // default.
+ setupZenConfig();
+
+ // Find default rules; check they have non-null policies; check that they match the default
+ // and not whatever has been set up in setupZenConfig.
+ ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
+ for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+ assertThat(rules).containsKey(defaultId);
+ ZenRule rule = rules.get(defaultId);
+ assertThat(rule.zenPolicy).isNotNull();
+
+ assertThat(rule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy());
+ }
+ }
+
+ @Test
public void testAddAutomaticZenRule_beyondSystemLimit() {
for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
ScheduleInfo si = new ScheduleInfo();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index f9fe6a9ac313..b431888a72fb 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -53,7 +53,6 @@ import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
-import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -270,10 +269,8 @@ public class VibrationScalerTest {
setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
- SparseArray<Float> adaptiveHapticsScales = new SparseArray<>();
- adaptiveHapticsScales.put(USAGE_RINGTONE, 0.5f);
- adaptiveHapticsScales.put(USAGE_NOTIFICATION, 0.5f);
- mVibrationScaler.updateAdaptiveHapticsScales(adaptiveHapticsScales);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.5f);
StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
@@ -287,6 +284,50 @@ public class VibrationScalerTest {
assertTrue(scaled.getAmplitude() < 0.5);
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void scale_clearAdaptiveHapticsScales_clearsAllCachedScales() {
+ setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
+ setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
+
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.5f);
+ mVibrationScaler.clearAdaptiveHapticsScales();
+
+ StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
+ // Ringtone scales up.
+ assertTrue(scaled.getAmplitude() > 0.5);
+
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
+ USAGE_NOTIFICATION));
+ // Notification scales up.
+ assertTrue(scaled.getAmplitude() > 0.5);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void scale_removeAdaptiveHapticsScale_removesCachedScale() {
+ setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
+ setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
+
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.5f);
+ mVibrationScaler.removeAdaptiveHapticsScale(USAGE_NOTIFICATION);
+
+ StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
+ // Ringtone scales down.
+ assertTrue(scaled.getAmplitude() < 0.5);
+
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
+ USAGE_NOTIFICATION));
+ // Notification scales up.
+ assertTrue(scaled.getAmplitude() > 0.5);
+ }
+
private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
@Vibrator.VibrationIntensity int intensity) {
when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index b0aef47d0900..6e478d836cca 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
@@ -54,12 +55,16 @@ import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.test.TestLooper;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -81,6 +86,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
@@ -95,6 +101,9 @@ public class VibrationThreadTest {
private static final int TEST_RAMP_STEP_DURATION = 5;
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@Mock private VibrationThread.VibratorManagerHooks mManagerHooks;
@@ -104,6 +113,7 @@ public class VibrationThreadTest {
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
private VibrationSettings mVibrationSettings;
+ private VibrationScaler mVibrationScaler;
private TestLooper mTestLooper;
private TestLooperAutoDispatcher mCustomTestLooperDispatcher;
private VibrationThread mThread;
@@ -132,6 +142,7 @@ public class VibrationThreadTest {
Context context = InstrumentationRegistry.getContext();
mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
mVibrationConfigMock);
+ mVibrationScaler = new VibrationScaler(context, mVibrationSettings);
mockVibrators(VIBRATOR_ID);
@@ -231,6 +242,45 @@ public class VibrationThreadTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void vibrate_singleWaveformWithAdaptiveHapticsScaling_scalesAmplitudesProperly() {
+ mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ new long[]{5, 5, 5}, new int[]{1, 1, 1}, -1);
+ CompletableFuture<Void> mRequestVibrationParamsFuture = CompletableFuture.runAsync(() -> {
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+ });
+ long vibrationId = startThreadAndDispatcher(effect, mRequestVibrationParamsFuture,
+ USAGE_RINGTONE);
+ waitForCompletion();
+
+ assertEquals(Arrays.asList(expectedOneShot(15)),
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
+ for (int i = 0; i < amplitudes.size(); i++) {
+ assertTrue(amplitudes.get(i) < 1 / 255f);
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void vibrate_withVibrationParamsRequestStalling_timeoutRequestAndApplyNoScaling() {
+ mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ new long[]{5, 5, 5}, new int[]{1, 1, 1}, -1);
+
+ CompletableFuture<Void> neverCompletingFuture = new CompletableFuture<>();
+ long vibrationId = startThreadAndDispatcher(effect, neverCompletingFuture, USAGE_RINGTONE);
+ waitForCompletion();
+
+ assertEquals(Arrays.asList(expectedOneShot(15)),
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+ assertEquals(expectedAmplitudes(1, 1, 1),
+ mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+ }
+
+ @Test
public void vibrate_singleVibratorRepeatingWaveform_runsVibrationUntilThreadCancelled()
throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
@@ -1610,10 +1660,26 @@ public class VibrationThreadTest {
}
private long startThreadAndDispatcher(HalVibration vib) {
+ return startThreadAndDispatcher(vib, /* requestVibrationParamsFuture= */ null);
+ }
+
+ private long startThreadAndDispatcher(VibrationEffect effect,
+ CompletableFuture<Void> requestVibrationParamsFuture, int usage) {
+ VibrationAttributes attrs = new VibrationAttributes.Builder()
+ .setUsage(usage)
+ .build();
+ HalVibration vib = new HalVibration(mVibrationToken,
+ CombinedVibration.createParallel(effect),
+ new Vibration.CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+ return startThreadAndDispatcher(vib, requestVibrationParamsFuture);
+ }
+
+ private long startThreadAndDispatcher(HalVibration vib,
+ CompletableFuture<Void> requestVibrationParamsFuture) {
mControllers = createVibratorControllers();
DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, mControllers);
- mVibrationConductor =
- new VibrationStepConductor(vib, mVibrationSettings, deviceAdapter, mManagerHooks);
+ mVibrationConductor = new VibrationStepConductor(vib, mVibrationSettings, deviceAdapter,
+ mVibrationScaler, requestVibrationParamsFuture, mManagerHooks);
assertTrue(mThread.runVibrationOnVibrationThread(mVibrationConductor));
return mVibrationConductor.getVibration().id;
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 1e0b1df4fc67..2823223e4859 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -18,29 +18,47 @@ package com.android.server.vibrator;
import static android.os.VibrationAttributes.USAGE_ALARM;
import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_MEDIA;
import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+import static android.os.VibrationAttributes.USAGE_UNKNOWN;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import android.content.ComponentName;
+import android.content.pm.PackageManagerInternal;
import android.frameworks.vibrator.ScaleParam;
import android.frameworks.vibrator.VibrationParam;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.os.test.TestLooper;
import android.util.SparseArray;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
public class VibratorControlServiceTest {
@@ -49,35 +67,45 @@ public class VibratorControlServiceTest {
@Mock
private VibrationScaler mMockVibrationScaler;
- @Captor
- private ArgumentCaptor<SparseArray<Float>> mVibrationScalesCaptor;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternalMock;
+ private FakeVibratorController mFakeVibratorController;
private VibratorControlService mVibratorControlService;
+ private VibrationSettings mVibrationSettings;
private final Object mLock = new Object();
@Before
public void setUp() throws Exception {
+ when(mPackageManagerInternalMock.getSystemUiServiceComponent())
+ .thenReturn(new ComponentName("", ""));
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
+
+ TestLooper testLooper = new TestLooper();
+ mVibrationSettings = new VibrationSettings(
+ ApplicationProvider.getApplicationContext(), new Handler(testLooper.getLooper()));
+
+ mFakeVibratorController = new FakeVibratorController();
mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(),
- mMockVibrationScaler, mLock);
+ mMockVibrationScaler, mVibrationSettings, mLock);
}
@Test
public void testRegisterVibratorController() throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
- mVibratorControlService.registerVibratorController(fakeController);
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
- assertThat(fakeController.isLinkedToDeath).isTrue();
+ assertThat(mFakeVibratorController.isLinkedToDeath).isTrue();
}
@Test
public void testUnregisterVibratorController_providingTheRegisteredController_performsRequest()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
- mVibratorControlService.registerVibratorController(fakeController);
- mVibratorControlService.unregisterVibratorController(fakeController);
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ mVibratorControlService.unregisterVibratorController(mFakeVibratorController);
- verify(mMockVibrationScaler).updateAdaptiveHapticsScales(null);
- assertThat(fakeController.isLinkedToDeath).isFalse();
+ verify(mMockVibrationScaler).clearAdaptiveHapticsScales();
+ assertThat(mFakeVibratorController.isLinkedToDeath).isFalse();
}
@Test
@@ -93,41 +121,80 @@ public class VibratorControlServiceTest {
}
@Test
+ public void testOnRequestVibrationParamsComplete_cachesAdaptiveHapticsScalesCorrectly()
+ throws RemoteException {
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ int timeoutInMillis = 10;
+ CompletableFuture<Void> future =
+ mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ timeoutInMillis);
+ IBinder token = mVibratorControlService.getRequestVibrationParamsToken();
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.onRequestVibrationParamsComplete(token,
+ generateVibrationParams(vibrationScales));
+
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
+ // Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
+ // notification and communication request usages.
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_COMMUNICATION_REQUEST, 0.4f);
+ verifyNoMoreInteractions(mMockVibrationScaler);
+
+ assertThat(future.isDone()).isTrue();
+ assertThat(future.isCompletedExceptionally()).isFalse();
+ }
+
+ @Test
+ public void testOnRequestVibrationParamsComplete_withIncorrectToken_ignoresRequest()
+ throws RemoteException, InterruptedException {
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ int timeoutInMillis = 10;
+ CompletableFuture<Void> unusedFuture =
+ mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ timeoutInMillis);
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.onRequestVibrationParamsComplete(new Binder(),
+ generateVibrationParams(vibrationScales));
+
+ verifyZeroInteractions(mMockVibrationScaler);
+ }
+
+ @Test
public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
- mVibratorControlService.registerVibratorController(fakeController);
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
SparseArray<Float> vibrationScales = new SparseArray<>();
vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
- fakeController);
-
- verify(mMockVibrationScaler).updateAdaptiveHapticsScales(mVibrationScalesCaptor.capture());
- SparseArray<Float> cachedVibrationScales = mVibrationScalesCaptor.getValue();
- assertThat(cachedVibrationScales.size()).isEqualTo(3);
- assertThat(cachedVibrationScales.keyAt(0)).isEqualTo(USAGE_ALARM);
- assertThat(cachedVibrationScales.valueAt(0)).isEqualTo(0.7f);
- assertThat(cachedVibrationScales.keyAt(1)).isEqualTo(USAGE_NOTIFICATION);
- assertThat(cachedVibrationScales.valueAt(1)).isEqualTo(0.4f);
+ mFakeVibratorController);
+
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
// Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
// notification and communication request usages.
- assertThat(cachedVibrationScales.keyAt(2)).isEqualTo(USAGE_COMMUNICATION_REQUEST);
- assertThat(cachedVibrationScales.valueAt(2)).isEqualTo(0.4f);
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_COMMUNICATION_REQUEST, 0.4f);
+ verifyNoMoreInteractions(mMockVibrationScaler);
}
@Test
public void testSetVibrationParams_withUnregisteredController_ignoresRequest()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
-
SparseArray<Float> vibrationScales = new SparseArray<>();
vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
- fakeController);
+ mFakeVibratorController);
verifyZeroInteractions(mMockVibrationScaler);
}
@@ -135,23 +202,72 @@ public class VibratorControlServiceTest {
@Test
public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
- mVibratorControlService.registerVibratorController(fakeController);
- mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM, fakeController);
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ int types = buildVibrationTypesMask(ScaleParam.TYPE_ALARM, ScaleParam.TYPE_NOTIFICATION);
- verify(mMockVibrationScaler).updateAdaptiveHapticsScales(null);
+ mVibratorControlService.clearVibrationParams(types, mFakeVibratorController);
+
+ verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_ALARM);
+ verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_NOTIFICATION);
+ // Clearing ScaleParam.TYPE_NOTIFICATION will clear vibration scaling for both
+ // notification and communication request usages.
+ verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_COMMUNICATION_REQUEST);
}
@Test
public void testClearVibrationParams_withUnregisteredController_ignoresRequest()
throws RemoteException {
- FakeVibratorController fakeController = new FakeVibratorController();
-
- mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM, fakeController);
+ mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM,
+ mFakeVibratorController);
verifyZeroInteractions(mMockVibrationScaler);
}
+ @Test
+ public void testRequestVibrationParams_createsFutureRequestProperly()
+ throws RemoteException {
+ int timeoutInMillis = 10;
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ CompletableFuture<Void> future =
+ mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ timeoutInMillis);
+ try {
+ future.orTimeout(timeoutInMillis, TimeUnit.MILLISECONDS).get();
+ } catch (Throwable ignored) {
+ }
+ assertThat(mFakeVibratorController.didRequestVibrationParams).isTrue();
+ assertThat(mFakeVibratorController.requestVibrationType).isEqualTo(
+ ScaleParam.TYPE_RINGTONE);
+ assertThat(mFakeVibratorController.requestTimeoutInMillis).isEqualTo(timeoutInMillis);
+ }
+
+ @Test
+ public void testShouldRequestVibrationParams_returnsTrueForVibrationsThatShouldRequestParams()
+ throws RemoteException {
+ int[] vibrations =
+ new int[]{USAGE_ALARM, USAGE_RINGTONE, USAGE_MEDIA, USAGE_TOUCH, USAGE_NOTIFICATION,
+ USAGE_HARDWARE_FEEDBACK, USAGE_UNKNOWN, USAGE_COMMUNICATION_REQUEST};
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+
+ for (int vibration : vibrations) {
+ assertThat(mVibratorControlService.shouldRequestVibrationParams(vibration))
+ .isEqualTo(ArrayUtils.contains(
+ mVibrationSettings.getRequestVibrationParamsForUsages(), vibration));
+ }
+ }
+
+ @Test
+ public void testShouldRequestVibrationParams_unregisteredVibratorController_returnsFalse()
+ throws RemoteException {
+ int[] vibrations =
+ new int[]{USAGE_ALARM, USAGE_RINGTONE, USAGE_MEDIA, USAGE_TOUCH, USAGE_NOTIFICATION,
+ USAGE_HARDWARE_FEEDBACK, USAGE_UNKNOWN, USAGE_COMMUNICATION_REQUEST};
+
+ for (int vibration : vibrations) {
+ assertThat(mVibratorControlService.shouldRequestVibrationParams(vibration)).isFalse();
+ }
+ }
+
private VibrationParam[] generateVibrationParams(SparseArray<Float> vibrationScales) {
List<VibrationParam> vibrationParamList = new ArrayList<>();
for (int i = 0; i < vibrationScales.size(); i++) {
@@ -173,4 +289,12 @@ public class VibratorControlServiceTest {
return vibrationParam;
}
+
+ private int buildVibrationTypesMask(int... types) {
+ int typesMask = 0;
+ for (int type : types) {
+ typesMask |= type;
+ }
+ return typesMask;
+ }
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index d6b2116e2682..bdbb6c6baa70 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -318,6 +318,12 @@ public class VibratorManagerServiceTest {
return new HapticFeedbackVibrationProvider(
resources, vibratorInfo, mHapticFeedbackVibrationMap);
}
+
+ VibratorControllerHolder createVibratorControllerHolder() {
+ VibratorControllerHolder holder = new VibratorControllerHolder();
+ holder.setVibratorController(new FakeVibratorController());
+ return holder;
+ }
});
return mService;
}
@@ -965,8 +971,9 @@ public class VibratorManagerServiceTest {
new long[]{10, 10_000}, new int[]{255, 0}, 1);
vibrate(service, repeatingEffect, ALARM_ATTRS);
- // VibrationThread will start this vibration async, wait until the off waveform step.
- assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
+ // VibrationThread will start this vibration async, wait until it has started.
+ assertTrue(waitUntil(s -> !fakeVibrator.getAllEffectSegments().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
ALARM_ATTRS);
@@ -1019,8 +1026,9 @@ public class VibratorManagerServiceTest {
new long[]{10, 10_000}, new int[]{255, 0}, -1);
vibrate(service, alarmEffect, ALARM_ATTRS);
- // VibrationThread will start this vibration async, wait until the off waveform step.
- assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
+ // VibrationThread will start this vibration async, wait until it has started.
+ assertTrue(waitUntil(s -> !fakeVibrator.getAllEffectSegments().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
UNKNOWN_ATTRS);
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
index 7e235870cedc..391220669f2f 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.frameworks.vibrator.IVibratorController;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.VibrationAttributes;
/**
* Provides a fake implementation of {@link android.frameworks.vibrator.IVibratorController} for
@@ -28,10 +29,16 @@ import android.os.RemoteException;
public final class FakeVibratorController extends IVibratorController.Stub {
public boolean isLinkedToDeath = false;
+ public boolean didRequestVibrationParams = false;
+ public int requestVibrationType = VibrationAttributes.USAGE_UNKNOWN;
+ public long requestTimeoutInMillis = 0;
@Override
- public void requestVibrationParams(int i, long l, IBinder iBinder) throws RemoteException {
-
+ public void requestVibrationParams(int vibrationType, long timeoutInMillis, IBinder iBinder)
+ throws RemoteException {
+ didRequestVibrationParams = true;
+ requestVibrationType = vibrationType;
+ requestTimeoutInMillis = timeoutInMillis;
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 0382ca0d9fec..e21388e57a9e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -34,6 +34,7 @@ import static android.view.KeyEvent.KEYCODE_SHIFT_LEFT;
import static android.view.KeyEvent.KEYCODE_SLASH;
import static android.view.KeyEvent.KEYCODE_SPACE;
import static android.view.KeyEvent.KEYCODE_TAB;
+import static android.view.KeyEvent.KEYCODE_SCREENSHOT;
import static android.view.KeyEvent.KEYCODE_U;
import static android.view.KeyEvent.KEYCODE_Z;
@@ -193,4 +194,26 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
mPhoneWindowManager.verifyNewBrightness(newBrightness[i]);
}
}
+
+ /**
+ * Sends a KEYCODE_SCREENSHOT and validates screenshot is taken if flag is enabled
+ */
+ @Test
+ public void testTakeScreenshot_flagEnabled() {
+ mSetFlagsRule.enableFlags(com.android.hardware.input.Flags
+ .FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE);
+ sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0);
+ mPhoneWindowManager.assertTakeScreenshotCalled();
+ }
+
+ /**
+ * Sends a KEYCODE_SCREENSHOT and validates screenshot is not taken if flag is disabled
+ */
+ @Test
+ public void testTakeScreenshot_flagDisabled() {
+ mSetFlagsRule.disableFlags(com.android.hardware.input.Flags
+ .FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE);
+ sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0);
+ mPhoneWindowManager.assertTakeScreenshotNotCalled();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 157d1627d993..fee6582c1ba4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -46,6 +46,7 @@ import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
import static java.util.Collections.unmodifiableMap;
import android.content.Context;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
@@ -57,11 +58,17 @@ import com.android.internal.util.test.FakeSettingsProviderRule;
import org.junit.After;
import org.junit.Rule;
+import org.junit.rules.RuleChain;
import java.util.Map;
class ShortcutKeyTestBase {
- @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ public final FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+ @Rule
+ public RuleChain rules = RuleChain.outerRule(mSettingsProviderRule).around(mSetFlagsRule);
TestPhoneWindowManager mPhoneWindowManager;
DispatchedKeyHandler mDispatchedKeyHandler = event -> false;
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index c8abd8d7297e..2904c0325d40 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -596,6 +596,11 @@ class TestPhoneWindowManager {
verify(mDisplayPolicy).takeScreenshot(anyInt(), anyInt());
}
+ void assertTakeScreenshotNotCalled() {
+ mTestLooper.dispatchAll();
+ verify(mDisplayPolicy, never()).takeScreenshot(anyInt(), anyInt());
+ }
+
void assertShowGlobalActionsCalled() {
mTestLooper.dispatchAll();
verify(mPhoneWindowManager).showGlobalActions();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index 09f677e701df..46e14d51b5fd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -16,8 +16,6 @@
package com.android.server.wm;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -38,13 +36,12 @@ import android.app.servertransaction.ClientTransactionItem;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
@@ -56,13 +53,8 @@ import org.mockito.MockitoAnnotations;
*/
@SmallTest
@Presubmit
-public class ClientLifecycleManagerTests {
-
- @Rule(order = 0)
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
-
- @Rule(order = 1)
- public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
+@RunWith(WindowTestRunner.class)
+public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
@Mock
private IBinder mClientBinder;
@@ -86,7 +78,7 @@ public class ClientLifecycleManagerTests {
public void setup() {
MockitoAnnotations.initMocks(this);
- mWms = mSystemServices.getWindowManagerService();
+ mWms = mSystemServicesTestRule.getWindowManagerService();
mLifecycleManager = spy(new ClientLifecycleManager());
mLifecycleManager.setWindowManager(mWms);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index be837441ef94..0ee78e38301c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1802,8 +1802,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
@Override
public void addStartingWindow(StartingWindowInfo info) {
synchronized (mWMService.mGlobalLock) {
- final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
- info.appToken);
+ final ActivityRecord activity = ActivityRecord.forTokenLocked(info.appToken);
IWindow iWindow = mock(IWindow.class);
doReturn(mock(IBinder.class)).when(iWindow).asBinder();
final WindowState window = WindowTestsBase.createWindow(null,
@@ -1825,8 +1824,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
final IBinder appToken = mTaskAppMap.get(removalInfo.taskId);
if (appToken != null) {
mTaskAppMap.remove(removalInfo.taskId);
- final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
- appToken);
+ final ActivityRecord activity = ActivityRecord.forTokenLocked(appToken);
WindowState win = mAppWindowMap.remove(appToken);
activity.removeChild(win);
activity.mStartingWindow = null;
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 7ccc27e2f94e..0c324e6061cb 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -649,12 +649,19 @@ public final class NetworkRegistrationInfo implements Parcelable {
}
/**
- * @return Reason for denial if the registration state is {@link #REGISTRATION_STATE_DENIED}.
- * Depending on {@code accessNetworkTechnology}, the values are defined in 3GPP TS 24.008
- * 10.5.3.6 for UMTS, 3GPP TS 24.301 9.9.3.9 for LTE, and 3GPP2 A.S0001 6.2.2.44 for CDMA
- * @hide
+ * Get the 3GPP/3GPP2 reason code indicating why registration failed.
+ *
+ * Returns the reason code for non-transient registration failures. Typically this method will
+ * only return the last reason code received during a network selection procedure. The reason
+ * code is system-specific; however, the reason codes for both 3GPP and 3GPP2 systems are
+ * largely equivalent across generations.
+ *
+ * @return registration reject cause if available, otherwise 0. Depending on
+ * {@link #getAccessNetworkTechnology}, the values are defined in 3GPP TS 24.008 10.5.3.6 for
+ * WCDMA/UMTS, 3GPP TS 24.301 9.9.3.9 for LTE/EPS, 3GPP 24.501 Annex A for NR/5GS, or 3GPP2
+ * A.S0001 6.2.2.44 for CDMA.
*/
- @SystemApi
+ @FlaggedApi(Flags.FLAG_NETWORK_REGISTRATION_INFO_REJECT_CAUSE)
public int getRejectCause() {
return mRejectCause;
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a0f033860b03..89661a4368ec 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6638,11 +6638,7 @@ public class TelephonyManager {
}
}
- /**
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
- private ITelephony getITelephony() {
+ private static ITelephony getITelephony() {
// Keeps cache disabled until test fixes are checked into AOSP.
if (!sServiceHandleCacheEnabled) {
return ITelephony.Stub.asInterface(
@@ -18696,11 +18692,7 @@ public class TelephonyManager {
@SimState
public static int getSimStateForSlotIndex(int slotIndex) {
try {
- ITelephony telephony = ITelephony.Stub.asInterface(
- TelephonyFrameworkInitializer
- .getTelephonyServiceManager()
- .getTelephonyServiceRegisterer()
- .get());
+ ITelephony telephony = getITelephony();
if (telephony != null) {
return telephony.getSimStateForSlotIndex(slotIndex);
}
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 09d21083afb1..9b8e62f2e5af 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -43,6 +43,7 @@ import android.telephony.euicc.EuiccCardManager.ResetOption;
import android.util.Log;
import com.android.internal.telephony.euicc.IEuiccController;
+import com.android.internal.telephony.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -897,9 +898,7 @@ public class EuiccManager {
/** @hide */
public EuiccManager(Context context) {
mContext = context;
- TelephonyManager tm = (TelephonyManager)
- context.getSystemService(Context.TELEPHONY_SERVICE);
- mCardId = tm.getCardIdForDefaultEuicc();
+ mCardId = getCardIdForDefaultEuicc();
}
/** @hide */
@@ -1646,14 +1645,9 @@ public class EuiccManager {
private boolean refreshCardIdIfUninitialized() {
// Refresh mCardId if its UNINITIALIZED_CARD_ID
if (mCardId == TelephonyManager.UNINITIALIZED_CARD_ID) {
- TelephonyManager tm = (TelephonyManager)
- mContext.getSystemService(Context.TELEPHONY_SERVICE);
- mCardId = tm.getCardIdForDefaultEuicc();
+ mCardId = getCardIdForDefaultEuicc();
}
- if (mCardId == TelephonyManager.UNINITIALIZED_CARD_ID) {
- return false;
- }
- return true;
+ return mCardId != TelephonyManager.UNINITIALIZED_CARD_ID;
}
private static void sendUnavailableError(PendingIntent callbackIntent) {
@@ -1672,6 +1666,23 @@ public class EuiccManager {
.get());
}
+ private int getCardIdForDefaultEuicc() {
+ int cardId = TelephonyManager.UNINITIALIZED_CARD_ID;
+
+ if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
+ PackageManager pm = mContext.getPackageManager();
+ if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC)) {
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+ cardId = tm.getCardIdForDefaultEuicc();
+ }
+ } else {
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+ cardId = tm.getCardIdForDefaultEuicc();
+ }
+
+ return cardId;
+ }
+
/**
* Returns whether the passing portIndex is available.
* A port is available if it is active without enabled profile on it or
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index 6015e9318464..381c57496878 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -101,7 +101,7 @@ public abstract class NetworkEvaluationTestBase {
@Mock protected Context mContext;
@Mock protected Network mNetwork;
@Mock protected FeatureFlags mFeatureFlags;
- @Mock protected com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;
+ @Mock protected android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
@Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot;
@Mock protected TelephonyManager mTelephonyManager;
@Mock protected IPowerManager mPowerManagerService;
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 275a0e21fedd..938a5ed23e28 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -232,3 +232,39 @@ genrule {
],
},
}
+
+cc_genrule {
+ name: "aapt2_results",
+ srcs: [
+ ":aapt2_tests",
+ "integration-tests/CompileTest/**/*",
+ "integration-tests/CommandTests/**/*",
+ "integration-tests/ConvertTest/**/*",
+ "integration-tests/DumpTest/**/*",
+ ],
+ host_supported: true,
+ device_supported: false,
+ target: {
+ windows: {
+ compile_multilib: "64",
+ },
+ },
+ out: ["result.xml"],
+ cmd: "mkdir -p $(genDir)/integration-tests/CompileTest/ && " +
+ "cp $(locations integration-tests/CompileTest/**/*) $(genDir)/integration-tests/CompileTest/ && " +
+ "mkdir -p $(genDir)/integration-tests/CommandTests/ && " +
+ "cp $(locations integration-tests/CommandTests/**/*) $(genDir)/integration-tests/CompileTest/ && " +
+ "mkdir -p $(genDir)/integration-tests/ConvertTest/ && " +
+ "cp $(locations integration-tests/ConvertTest/**/*) $(genDir)/integration-tests/ConvertTest/ && " +
+ "mkdir -p $(genDir)/integration-tests/DumpTest/ && " +
+ "cp $(locations integration-tests/DumpTest/**/*) $(genDir)/integration-tests/DumpTest/ && " +
+ "cp $(locations :aapt2_tests) $(genDir)/ && " +
+ "$(genDir)/aapt2_tests " +
+ "--gtest_output=xml:$(out) " +
+ ">/dev/null 2>&1 ; true",
+}
+
+phony_rule {
+ name: "aapt2_run_host_unit_tests",
+ phony_deps: ["aapt2_results"],
+}
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 34a1b112d880..15ae2baa73df 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -1,22 +1,4 @@
-LOCAL_PATH := $(call my-dir)
-
include $(CLEAR_VARS)
-
-aapt2_results := $(call intermediates-dir-for,PACKAGING,aapt2_run_host_unit_tests)/result.xml
-
-# Target for running host unit tests on post/pre-submit.
-.PHONY: aapt2_run_host_unit_tests
-aapt2_run_host_unit_tests: $(aapt2_results)
-
-$(call dist-for-goals,aapt2_run_host_unit_tests,$(aapt2_results):gtest/aapt2_host_unit_tests_result.xml)
-
-# Always run the tests again, even if they haven't changed
-$(aapt2_results): .KATI_IMPLICIT_OUTPUTS := $(aapt2_results)-nocache
-$(aapt2_results): $(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests
- -$(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests --gtest_output=xml:$@ > /dev/null 2>&1
-
+aapt2_results := ./out/soong/.intermediates/frameworks/base/tools/aapt2/aapt2_results
$(call declare-1p-target,$(aapt2_results))
-
aapt2_results :=
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java
index 292e8da0de10..6480cfc2b492 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java
@@ -15,9 +15,9 @@
*/
package com.android.hoststubgen.nativesubstitution;
-import android.util.Log;
-import android.util.Log.Level;
+import com.android.internal.os.RuntimeInit;
+import java.io.PrintStream;
import java.util.Collection;
public class EventLog_host {
@@ -54,7 +54,7 @@ public class EventLog_host {
}
}
sb.append(']');
- System.out.println(sb.toString());
+ getRealOut().println(sb.toString());
return sb.length();
}
@@ -66,4 +66,16 @@ public class EventLog_host {
Collection<android.util.EventLog.Event> output) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * Return the "real" {@code System.out} if it's been swapped by {@code RavenwoodRuleImpl}, so
+ * that we don't end up in a recursive loop.
+ */
+ private static PrintStream getRealOut() {
+ if (RuntimeInit.sOut$ravenwood != null) {
+ return RuntimeInit.sOut$ravenwood;
+ } else {
+ return System.out;
+ }
+ }
}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java
index ee55c7ac6ae2..cdfa30276961 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java
@@ -18,6 +18,8 @@ package com.android.hoststubgen.nativesubstitution;
import android.util.Log;
import android.util.Log.Level;
+import com.android.internal.os.RuntimeInit;
+
import java.io.PrintStream;
public class Log_host {
@@ -27,7 +29,6 @@ public class Log_host {
}
public static int println_native(int bufID, int priority, String tag, String msg) {
- final PrintStream out = System.out;
final String buffer;
switch (bufID) {
case Log.LOG_ID_MAIN: buffer = "main"; break;
@@ -50,7 +51,7 @@ public class Log_host {
};
for (String s : msg.split("\\n")) {
- out.println(String.format("logd: [%s] %s %s: %s", buffer, prio, tag, s));
+ getRealOut().println(String.format("logd: [%s] %s %s: %s", buffer, prio, tag, s));
}
return msg.length();
}
@@ -58,4 +59,16 @@ public class Log_host {
public static int logger_entry_max_payload_native() {
return 4068; // [ravenwood] This is what people use in various places.
}
+
+ /**
+ * Return the "real" {@code System.out} if it's been swapped by {@code RavenwoodRuleImpl}, so
+ * that we don't end up in a recursive loop.
+ */
+ private static PrintStream getRealOut() {
+ if (RuntimeInit.sOut$ravenwood != null) {
+ return RuntimeInit.sOut$ravenwood;
+ } else {
+ return System.out;
+ }
+ }
}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index 7c6aa25bf5ea..60eb47eea7c7 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -63,7 +63,10 @@ public class HostTestUtils {
*/
public static void onThrowMethodCalled() {
// TODO: Maybe add call tracking?
- throw new RuntimeException("This method is not supported on the host side");
+ throw new RuntimeException(
+ "This method is not yet supported under the Ravenwood deviceless testing "
+ + "environment; consider requesting support from the API owner or "
+ + "consider using Mockito; more details at go/ravenwood-docs");
}
/**
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index fc6b862705f8..ba17c75132f2 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -68,7 +68,7 @@ public class TinyFrameworkClassTest {
TinyFrameworkForTextPolicy tfc = new TinyFrameworkForTextPolicy();
thrown.expect(RuntimeException.class);
- thrown.expectMessage("This method is not supported on the host side");
+ thrown.expectMessage("not yet supported");
tfc.visibleButUsesUnsupportedMethod();
}
@@ -182,7 +182,7 @@ public class TinyFrameworkClassTest {
} catch (java.lang.reflect.InvocationTargetException e) {
var inner = e.getCause();
- assertThat(inner.getMessage()).contains("not supported on the host side");
+ assertThat(inner.getMessage()).contains("not yet supported");
}
}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
index 20cc2ec9d50d..288c7162aa58 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
@@ -60,7 +60,7 @@ public class TinyFrameworkClassWithAnnotTest {
TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
thrown.expect(RuntimeException.class);
- thrown.expectMessage("This method is not supported on the host side");
+ thrown.expectMessage("not yet supported");
tfc.visibleButUsesUnsupportedMethod();
}
}
diff --git a/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java b/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
index 21700d5852cb..6d57a8709ab9 100644
--- a/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
+++ b/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
@@ -16,11 +16,13 @@
package android.net.wifi.nl80211;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiAnnotations.ChannelWidth;
import android.net.wifi.WifiAnnotations.WifiStandard;
+import android.net.wifi.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
@@ -48,6 +50,7 @@ public final class DeviceWiphyCapabilities implements Parcelable {
private boolean mChannelWidth320MhzSupported;
private int mMaxNumberTxSpatialStreams;
private int mMaxNumberRxSpatialStreams;
+ private int mMaxNumberAkms;
/** public constructor */
@@ -61,6 +64,7 @@ public final class DeviceWiphyCapabilities implements Parcelable {
mChannelWidth320MhzSupported = false;
mMaxNumberTxSpatialStreams = 1;
mMaxNumberRxSpatialStreams = 1;
+ mMaxNumberAkms = 1;
}
/**
@@ -199,6 +203,25 @@ public final class DeviceWiphyCapabilities implements Parcelable {
}
/**
+ * Get the maximum number of AKM suites supported in the connection request to the driver.
+ *
+ * @return maximum number of AKMs
+ */
+ @FlaggedApi(Flags.FLAG_GET_DEVICE_CROSS_AKM_ROAMING_SUPPORT)
+ public int getMaxNumberAkms() {
+ return mMaxNumberAkms;
+ }
+
+ /**
+ * Set the maximum number of AKM suites supported in the connection request to the driver.
+ *
+ * @hide
+ */
+ public void setMaxNumberAkms(int akms) {
+ mMaxNumberAkms = akms;
+ }
+
+ /**
* Set maximum number of receive spatial streams
*
* @param streams number of streams
@@ -226,7 +249,8 @@ public final class DeviceWiphyCapabilities implements Parcelable {
&& mChannelWidth80p80MhzSupported == capa.mChannelWidth80p80MhzSupported
&& mChannelWidth320MhzSupported == capa.mChannelWidth320MhzSupported
&& mMaxNumberTxSpatialStreams == capa.mMaxNumberTxSpatialStreams
- && mMaxNumberRxSpatialStreams == capa.mMaxNumberRxSpatialStreams;
+ && mMaxNumberRxSpatialStreams == capa.mMaxNumberRxSpatialStreams
+ && mMaxNumberAkms == capa.mMaxNumberAkms;
}
/** override hash code */
@@ -235,7 +259,7 @@ public final class DeviceWiphyCapabilities implements Parcelable {
return Objects.hash(m80211nSupported, m80211acSupported, m80211axSupported,
m80211beSupported, mChannelWidth160MhzSupported, mChannelWidth80p80MhzSupported,
mChannelWidth320MhzSupported, mMaxNumberTxSpatialStreams,
- mMaxNumberRxSpatialStreams);
+ mMaxNumberRxSpatialStreams, mMaxNumberAkms);
}
/** implement Parcelable interface */
@@ -259,6 +283,7 @@ public final class DeviceWiphyCapabilities implements Parcelable {
out.writeBoolean(mChannelWidth320MhzSupported);
out.writeInt(mMaxNumberTxSpatialStreams);
out.writeInt(mMaxNumberRxSpatialStreams);
+ out.writeInt(mMaxNumberAkms);
}
@Override
@@ -276,6 +301,7 @@ public final class DeviceWiphyCapabilities implements Parcelable {
.append(mChannelWidth320MhzSupported ? "Yes" : "No");
sb.append("mMaxNumberTxSpatialStreams: ").append(mMaxNumberTxSpatialStreams);
sb.append("mMaxNumberRxSpatialStreams: ").append(mMaxNumberRxSpatialStreams);
+ sb.append("mMaxNumberAkms: ").append(mMaxNumberAkms);
return sb.toString();
}
@@ -298,6 +324,7 @@ public final class DeviceWiphyCapabilities implements Parcelable {
capabilities.mChannelWidth320MhzSupported = in.readBoolean();
capabilities.mMaxNumberTxSpatialStreams = in.readInt();
capabilities.mMaxNumberRxSpatialStreams = in.readInt();
+ capabilities.mMaxNumberAkms = in.readInt();
return capabilities;
}
diff --git a/wifi/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java b/wifi/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java
index 7b900fec70a8..5a3ca2beee14 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java
@@ -49,6 +49,7 @@ public class DeviceWiphyCapabilitiesTest {
capa.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ, false);
capa.setMaxNumberTxSpatialStreams(2);
capa.setMaxNumberRxSpatialStreams(1);
+ capa.setMaxNumberAkms(2);
Parcel parcel = Parcel.obtain();
capa.writeToParcel(parcel, 0);
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 362eb1425a30..02da835d1002 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -1130,6 +1130,7 @@ public class WifiNl80211ManagerTest {
capaExpected.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ, false);
capaExpected.setMaxNumberTxSpatialStreams(2);
capaExpected.setMaxNumberRxSpatialStreams(1);
+ capaExpected.setMaxNumberAkms(2);
when(mWificond.getDeviceWiphyCapabilities(TEST_INTERFACE_NAME))
.thenReturn(capaExpected);
diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig
new file mode 100644
index 000000000000..6ac986e406a0
--- /dev/null
+++ b/wifi/wifi.aconfig
@@ -0,0 +1,9 @@
+package: "android.net.wifi.flags"
+
+flag {
+ name: "get_device_cross_akm_roaming_support"
+ namespace: "wifi"
+ description: "Add new API to get the device support for CROSS-AKM roaming"
+ bug: "313038031"
+ is_fixed_read_only: true
+}