summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp14
-rw-r--r--FF_LEADS_OWNERS10
-rw-r--r--apex/jobscheduler/service/aconfig/alarm.aconfig10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java87
-rw-r--r--core/api/current.txt117
-rw-r--r--core/api/lint-baseline.txt11
-rw-r--r--core/api/module-lib-current.txt30
-rw-r--r--core/api/system-current.txt155
-rw-r--r--core/api/test-current.txt7
-rw-r--r--core/api/test-lint-baseline.txt6
-rw-r--r--core/java/Android.bp2
-rw-r--r--core/java/android/app/AppOpsManager.java33
-rw-r--r--core/java/android/app/ApplicationPackageManager.java42
-rw-r--r--core/java/android/app/IActivityClientController.aidl4
-rw-r--r--core/java/android/app/IActivityManager.aidl2
-rw-r--r--core/java/android/app/PropertyInvalidatedCache.java16
-rw-r--r--core/java/android/app/SystemServiceRegistry.java29
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java67
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl2
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig12
-rw-r--r--core/java/android/app/backup/BackupTransport.java33
-rw-r--r--core/java/android/app/supervision/SupervisionManagerInternal.java (renamed from services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java)11
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig8
-rw-r--r--core/java/android/content/pm/PackageManager.java74
-rw-r--r--core/java/android/content/pm/SharedLibraryInfo.java43
-rw-r--r--core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl19
-rw-r--r--core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java100
-rw-r--r--core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java83
-rw-r--r--core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl41
-rw-r--r--core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl37
-rw-r--r--core/java/android/content/pm/flags.aconfig10
-rw-r--r--core/java/android/hardware/DisplayLuts.java286
-rw-r--r--core/java/android/hardware/LutProperties.java27
-rw-r--r--core/java/android/hardware/OverlayProperties.java7
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java62
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java55
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java156
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java32
-rw-r--r--core/java/android/hardware/camera2/CameraSharedCaptureSession.java180
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceImpl.java173
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java26
-rw-r--r--core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java16
-rw-r--r--core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java242
-rw-r--r--core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java12
-rw-r--r--core/java/android/hardware/camera2/params/OutputConfiguration.java74
-rw-r--r--core/java/android/hardware/camera2/params/SessionConfiguration.java14
-rw-r--r--core/java/android/hardware/camera2/params/SharedSessionConfiguration.java312
-rw-r--r--core/java/android/hardware/contexthub/HubDiscoveryInfo.java56
-rw-r--r--core/java/android/hardware/contexthub/HubEndpoint.java406
-rw-r--r--core/java/android/hardware/contexthub/HubEndpointInfo.aidl20
-rw-r--r--core/java/android/hardware/contexthub/HubEndpointInfo.java196
-rw-r--r--core/java/android/hardware/contexthub/HubEndpointSession.java115
-rw-r--r--core/java/android/hardware/contexthub/HubEndpointSessionResult.java79
-rw-r--r--core/java/android/hardware/contexthub/IContextHubEndpoint.aidl65
-rw-r--r--core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl50
-rw-r--r--core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java78
-rw-r--r--core/java/android/hardware/contexthub/OWNERS2
-rw-r--r--core/java/android/hardware/display/DisplayManager.java22
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java18
-rw-r--r--core/java/android/hardware/flags/flags.aconfig (renamed from core/java/android/hardware/flags/overlayproperties_flags.aconfig)9
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig9
-rw-r--r--core/java/android/hardware/location/ContextHubManager.java78
-rw-r--r--core/java/android/hardware/location/IContextHubService.aidl21
-rw-r--r--core/java/android/net/ConnectivityFrameworkInitializerBaklava.java (renamed from core/java/android/net/vcn/VcnFrameworkInitializer.java)18
-rw-r--r--core/java/android/net/vcn/VcnGatewayConnectionConfig.java19
-rw-r--r--core/java/android/net/vcn/VcnTransportInfo.java100
-rw-r--r--core/java/android/os/Bundle.java13
-rw-r--r--core/java/android/os/CombinedMessageQueue/MessageQueue.java33
-rw-r--r--core/java/android/os/CpuHeadroomParams.java91
-rw-r--r--core/java/android/os/CpuHeadroomParamsInternal.aidl31
-rw-r--r--core/java/android/os/GpuHeadroomParams.java91
-rw-r--r--core/java/android/os/GpuHeadroomParamsInternal.aidl28
-rw-r--r--core/java/android/os/IHintManager.aidl6
-rw-r--r--core/java/android/os/OWNERS6
-rw-r--r--core/java/android/os/UserManager.java19
-rw-r--r--core/java/android/os/flags.aconfig33
-rw-r--r--core/java/android/os/health/SystemHealthManager.java111
-rw-r--r--core/java/android/permission/flags.aconfig11
-rw-r--r--core/java/android/security/flags.aconfig8
-rw-r--r--core/java/android/security/forensic/ForensicManager.java276
-rw-r--r--core/java/android/security/forensic/IForensicEventTransport.aidl (renamed from core/java/android/security/forensic/IBackupTransport.aidl)2
-rw-r--r--core/java/android/security/forensic/IForensicService.aidl9
-rw-r--r--core/java/android/security/forensic/IForensicServiceCommandCallback.aidl4
-rw-r--r--core/java/android/security/forensic/IForensicServiceStateCallback.aidl5
-rw-r--r--core/java/android/service/autofill/FillEventHistory.java51
-rw-r--r--core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl2
-rw-r--r--core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl2
-rw-r--r--core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java18
-rw-r--r--core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java32
-rw-r--r--core/java/android/service/quickaccesswallet/QuickAccessWalletService.java42
-rw-r--r--core/java/android/service/quickaccesswallet/flags.aconfig2
-rw-r--r--core/java/android/telephony/SubscriptionPlan.java71
-rw-r--r--core/java/android/text/TextFlags.java81
-rw-r--r--core/java/android/text/TextUtils.java9
-rw-r--r--core/java/android/text/style/NoWritingToolsSpan.java87
-rw-r--r--core/java/android/view/Choreographer.java4
-rw-r--r--core/java/android/view/SurfaceControl.java29
-rw-r--r--core/java/android/view/ViewRootImpl.java5
-rw-r--r--core/java/android/view/ViewStructure.java16
-rw-r--r--core/java/android/view/autofill/AutofillId.java43
-rw-r--r--core/java/android/view/flags/scroll_feedback_flags.aconfig2
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java28
-rw-r--r--core/java/android/view/inputmethod/flags.aconfig9
-rw-r--r--core/java/android/widget/TextView.java581
-rw-r--r--core/java/android/window/TransitionInfo.java10
-rw-r--r--core/java/android/window/WindowContainerTransaction.java4
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig7
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig11
-rw-r--r--core/java/com/android/internal/backup/IBackupTransport.aidl11
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java10
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java19
-rw-r--r--core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java17
-rw-r--r--core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java4
-rw-r--r--core/jni/android_hardware_OverlayProperties.cpp2
-rw-r--r--core/jni/android_util_Process.cpp3
-rw-r--r--core/jni/android_view_SurfaceControl.cpp114
-rw-r--r--core/res/AndroidManifest.xml132
-rw-r--r--core/res/res/layout/list_content_simple.xml1
-rw-r--r--core/res/res/values/attrs.xml30
-rw-r--r--core/res/res/values/attrs_manifest.xml15
-rw-r--r--core/res/res/values/config.xml17
-rw-r--r--core/res/res/values/config_telephony.xml6
-rw-r--r--core/res/res/values/public-staging.xml5
-rw-r--r--core/res/res/values/symbols.xml9
-rw-r--r--core/res/res/xml/bookmarks.xml26
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java15
-rw-r--r--core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java69
-rw-r--r--core/tests/coretests/src/android/os/OWNERS3
-rw-r--r--data/etc/privapp-permissions-platform.xml9
-rw-r--r--graphics/java/android/graphics/BlendMode.java6
-rw-r--r--graphics/java/android/graphics/ComposeShader.java5
-rw-r--r--graphics/java/android/graphics/Paint.java36
-rw-r--r--graphics/java/android/graphics/PorterDuffXfermode.java3
-rw-r--r--graphics/java/android/graphics/RuntimeColorFilter.java17
-rw-r--r--graphics/java/android/graphics/RuntimeShader.java44
-rw-r--r--graphics/java/android/graphics/RuntimeXfermode.java329
-rw-r--r--graphics/java/android/graphics/Xfermode.java9
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java1
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java1
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java12
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java14
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java34
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml3
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java25
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java203
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt235
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt3
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontLandscape.kt50
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontPortrait.kt50
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt25
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWindowWithDragToTopDragZoneLandscape.kt51
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWindowWithDragToTopDragZonePortrait.kt48
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/BringDesktopAppsToFrontTest.kt26
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/BringDesktopAppsToFront.kt72
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowSingleWindow.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximiseAppWithCornerResize.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestSyncExecutor.kt38
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt75
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt60
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java58
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java65
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java31
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt387
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java22
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java41
-rw-r--r--libs/hwui/Android.bp1
-rw-r--r--libs/hwui/apex/jni_runtime.cpp2
-rw-r--r--libs/hwui/jni/Paint.cpp8
-rw-r--r--libs/hwui/jni/RuntimeEffectUtils.cpp2
-rw-r--r--libs/hwui/jni/RuntimeXfermode.cpp117
-rw-r--r--libs/hwui/jni/Shader.cpp69
-rw-r--r--media/java/android/media/AudioPlaybackConfiguration.java26
-rw-r--r--media/java/android/media/AudioRecord.java19
-rw-r--r--media/java/android/media/AudioRouting.java23
-rw-r--r--media/java/android/media/AudioTrack.java23
-rw-r--r--media/java/android/media/ImageWriter.java6
-rw-r--r--media/java/android/media/MediaCas.java5
-rw-r--r--media/java/android/media/MediaPlayer.java22
-rw-r--r--media/java/android/media/MediaRecorder.java21
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig153
-rw-r--r--media/java/android/media/flags/projection.aconfig7
-rw-r--r--media/java/android/media/quality/AmbientBacklightEvent.java7
-rw-r--r--media/java/android/media/quality/AmbientBacklightMetadata.java27
-rw-r--r--media/java/android/media/quality/AmbientBacklightSettings.java29
-rw-r--r--media/java/android/media/quality/IMediaQualityManager.aidl4
-rw-r--r--media/java/android/media/quality/ISoundProfileCallback.aidl9
-rw-r--r--media/java/android/media/quality/MediaQualityManager.java163
-rw-r--r--media/java/android/media/quality/SoundProfile.java226
-rw-r--r--media/java/android/media/tv/ad/TvAdView.java32
-rw-r--r--media/java/android/media/tv/extension/oad/IOadUpdateInterface.aidl41
-rw-r--r--media/java/android/media/tv/extension/rating/IDownloadableRatingTableMonitor.aidl (renamed from core/java/android/text/ClientFlags.java)16
-rw-r--r--media/java/android/media/tv/extension/rating/IPmtRatingInterface.aidl31
-rw-r--r--media/java/android/media/tv/extension/rating/IPmtRatingListener.aidl24
-rw-r--r--media/java/android/media/tv/extension/rating/IProgramRatingInfo.aidl32
-rw-r--r--media/java/android/media/tv/extension/rating/IProgramRatingInfoListener.aidl26
-rw-r--r--media/java/android/media/tv/extension/rating/IRatingInterface.aidl31
-rw-r--r--media/java/android/media/tv/extension/rating/IVbiRatingInterface.aidl31
-rw-r--r--media/java/android/media/tv/extension/rating/IVbiRatingListener.aidl24
-rw-r--r--media/java/android/media/tv/extension/time/IBroadcastTime.aidl30
-rw-r--r--media/java/android/media/tv/flags/media_tv.aconfig26
-rw-r--r--media/java/android/media/tv/interactive/TvInteractiveAppView.java32
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java19
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java12
-rw-r--r--native/android/libandroid.map.txt4
-rw-r--r--native/android/performance_hint.cpp199
-rw-r--r--native/android/tests/performance_hint/PerformanceHintNativeTest.cpp41
-rw-r--r--native/graphics/jni/Android.bp2
-rw-r--r--nfc/api/system-current.txt17
-rw-r--r--nfc/java/android/nfc/INfcAdapter.aidl1
-rw-r--r--nfc/java/android/nfc/INfcCardEmulation.aidl2
-rw-r--r--nfc/java/android/nfc/INfcOemExtensionCallback.aidl3
-rw-r--r--nfc/java/android/nfc/NfcOemExtension.java96
-rw-r--r--nfc/java/android/nfc/cardemulation/CardEmulation.java79
-rw-r--r--nfc/java/android/nfc/flags.aconfig8
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt22
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt4
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt12
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoConverters.kt40
-rw-r--r--packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt35
-rw-r--r--packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt5
-rw-r--r--packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt9
-rw-r--r--packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt21
-rw-r--r--packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt9
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java5
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java33
-rw-r--r--packages/SettingsProvider/res/xml/bookmarks.xml3
-rw-r--r--packages/Shell/AndroidManifest.xml10
-rw-r--r--packages/Shell/aconfig/Android.bp13
-rw-r--r--packages/Shell/aconfig/wear.aconfig9
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig9
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt545
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt25
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt30
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToCommunalTransition.kt33
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt20
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt20
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt30
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt19
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt10
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt40
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt94
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt14
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt21
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt76
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt289
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt17
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt13
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt16
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt16
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt9
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt15
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt90
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt34
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt4
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt30
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt8
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt35
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt2
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt15
-rw-r--r--packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml1
-rw-r--r--packages/SystemUI/customization/res/values-sw600dp/dimens.xml1
-rw-r--r--packages/SystemUI/customization/res/values-sw720dp-land/dimens.xml19
-rw-r--r--packages/SystemUI/customization/res/values-sw720dp-port/dimens.xml19
-rw-r--r--packages/SystemUI/customization/res/values/dimens.xml1
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt252
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt29
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt45
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt40
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt33
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt21
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt7
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt58
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt47
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt62
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt58
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt33
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt125
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt228
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt116
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt70
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt68
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt4
-rw-r--r--packages/SystemUI/res/drawable/volume_background_top.xml1
-rw-r--r--packages/SystemUI/res/drawable/volume_dialog_spacer.xml23
-rw-r--r--packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml16
-rw-r--r--packages/SystemUI/res/layout/volume_dialog.xml102
-rw-r--r--packages/SystemUI/res/layout/volume_ringer_drawer.xml12
-rw-r--r--packages/SystemUI/res/values-sw600dp-land/dimens.xml2
-rw-r--r--packages/SystemUI/res/values-sw600dp-port/dimens.xml2
-rw-r--r--packages/SystemUI/res/values-sw720dp-land/dimens.xml2
-rw-r--r--packages/SystemUI/res/values-sw720dp-port/dimens.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml21
-rw-r--r--packages/SystemUI/res/xml/volume_dialog_constraint_set.xml15
-rw-r--r--packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml15
-rw-r--r--packages/SystemUI/res/xml/volume_dialog_scene.xml27
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt (renamed from packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt (renamed from packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt (renamed from packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/disableflags/domain/interactor/DisableFlagsInteractor.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/disableflags/shared/model/DisableFlagsModel.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt)18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastUI.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt129
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt2
-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/disableflags/data/repository/FakeDisableFlagsRepository.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/domain/interactor/DisableFlagsInteractorKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt18
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.kt48
-rwxr-xr-xravenwood/scripts/run-ravenwood-tests.sh16
-rw-r--r--services/autofill/features.aconfig7
-rw-r--r--services/autofill/java/com/android/server/autofill/Helper.java55
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java5
-rw-r--r--services/backup/flags.aconfig9
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java112
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java25
-rw-r--r--services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java24
-rw-r--r--services/backup/java/com/android/server/backup/transport/BackupTransportClient.java22
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java60
-rw-r--r--services/core/java/com/android/server/am/BackupRecord.java5
-rw-r--r--services/core/java/com/android/server/am/BroadcastController.java2
-rw-r--r--services/core/java/com/android/server/am/BroadcastFilter.java29
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java30
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java3
-rw-r--r--services/core/java/com/android/server/am/ProcessServiceRecord.java2
-rw-r--r--services/core/java/com/android/server/am/flags.aconfig10
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java9
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java6
-rw-r--r--services/core/java/com/android/server/biometrics/PreAuthInfo.java8
-rw-r--r--services/core/java/com/android/server/compat/PlatformCompat.java2
-rw-r--r--services/core/java/com/android/server/display/color/TintController.java39
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/input/AppLaunchShortcutManager.java34
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java11
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java93
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java37
-rw-r--r--services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java113
-rw-r--r--services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java49
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java122
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionStopController.java222
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java11
-rw-r--r--services/core/java/com/android/server/notification/GroupHelper.java116
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java9
-rw-r--r--services/core/java/com/android/server/pm/BroadcastHelper.java35
-rw-r--r--services/core/java/com/android/server/pm/InstallDependencyHelper.java217
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java11
-rw-r--r--services/core/java/com/android/server/pm/SharedLibrariesImpl.java5
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java14
-rw-r--r--services/core/java/com/android/server/policy/ModifierShortcutManager.java34
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java1
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java3
-rw-r--r--services/core/java/com/android/server/power/hint/HintManagerService.java282
-rw-r--r--services/core/java/com/android/server/security/forensic/ForensicEventTransportConnection.java (renamed from services/core/java/com/android/server/security/forensic/BackupTransportConnection.java)48
-rw-r--r--services/core/java/com/android/server/security/forensic/ForensicService.java208
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java5
-rw-r--r--services/core/java/com/android/server/wm/SnapshotPersistQueue.java15
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java22
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java7
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java3
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java21
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java607
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java9
-rw-r--r--services/java/com/android/server/SystemServer.java13
-rw-r--r--services/java/com/android/server/flags.aconfig8
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionService.java12
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionUserData.java4
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java51
-rw-r--r--services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java12
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java86
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java53
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java15
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastFilterTest.java21
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java30
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java60
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java33
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java197
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java108
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java21
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java102
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java20
-rw-r--r--services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java186
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java6
-rw-r--r--services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java347
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java25
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java59
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java46
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java429
-rw-r--r--services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java360
-rw-r--r--services/tests/wmtests/res/xml/bookmarks.xml47
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java55
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt44
-rw-r--r--tests/Input/res/xml/bookmarks.xml34
-rw-r--r--tests/Input/res/xml/bookmarks_legacy.xml60
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt186
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java35
-rw-r--r--tools/aapt2/link/TableMerger.cpp15
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java2
541 files changed, 17023 insertions, 4317 deletions
diff --git a/Android.bp b/Android.bp
index 48f0928f24d7..54cb2684068d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -107,7 +107,6 @@ filegroup {
":android.hardware.radio.data-V3-java-source",
":android.hardware.radio.network-V3-java-source",
":android.hardware.radio.voice-V3-java-source",
- ":android.hardware.security.keymint-V3-java-source",
":android.hardware.security.secureclock-V1-java-source",
":android.hardware.thermal-V3-java-source",
":android.hardware.tv.tuner-V3-java-source",
@@ -116,7 +115,6 @@ filegroup {
":android.security.legacykeystore-java-source",
":android.security.maintenance-java-source",
":android.security.metrics-java-source",
- ":android.system.keystore2-V4-java-source",
":android.hardware.cas-V1-java-source",
":credstore_aidl",
":dumpstate_aidl",
@@ -149,7 +147,16 @@ filegroup {
":statslog-framework-java-gen", // FrameworkStatsLog.java
":statslog-hwui-java-gen", // HwuiStatsLog.java
":audio_policy_configuration_V7_0",
- ],
+ ] + select(release_flag("RELEASE_ATTEST_MODULES"), {
+ true: [
+ ":android.hardware.security.keymint-V4-java-source",
+ ":android.system.keystore2-V5-java-source",
+ ],
+ default: [
+ ":android.hardware.security.keymint-V3-java-source",
+ ":android.system.keystore2-V4-java-source",
+ ],
+ }),
}
java_library {
@@ -398,6 +405,7 @@ java_defaults {
"bouncycastle-repackaged-unbundled",
"com.android.sysprop.foldlockbehavior",
"com.android.sysprop.view",
+ "configinfra_framework_flags_java_lib",
"framework-internal-utils",
"dynamic_instrumentation_manager_aidl-java",
// If MimeMap ever becomes its own APEX, then this dependency would need to be removed
diff --git a/FF_LEADS_OWNERS b/FF_LEADS_OWNERS
new file mode 100644
index 000000000000..a650c6b7a26f
--- /dev/null
+++ b/FF_LEADS_OWNERS
@@ -0,0 +1,10 @@
+bills@google.com
+carmenjackson@google.com
+nalini@google.com
+nosh@google.com
+olilan@google.com
+philipcuadra@google.com
+rajekumar@google.com
+shayba@google.com
+timmurray@google.com
+zezeozue@google.com
diff --git a/apex/jobscheduler/service/aconfig/alarm.aconfig b/apex/jobscheduler/service/aconfig/alarm.aconfig
index d3068d7d37e8..a6e980726a9a 100644
--- a/apex/jobscheduler/service/aconfig/alarm.aconfig
+++ b/apex/jobscheduler/service/aconfig/alarm.aconfig
@@ -2,16 +2,6 @@ package: "com.android.server.alarm"
container: "system"
flag {
- name: "use_frozen_state_to_drop_listener_alarms"
- namespace: "backstage_power"
- description: "Use frozen state callback to drop listener alarms for cached apps"
- bug: "324470945"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "start_user_before_scheduled_alarms"
namespace: "multiuser"
description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due"
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 033da2df9bf6..60ba3b896a28 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -282,7 +282,6 @@ public class AlarmManagerService extends SystemService {
private final Injector mInjector;
int mBroadcastRefCount = 0;
- boolean mUseFrozenStateToDropListenerAlarms;
MetricsHelper mMetricsHelper;
PowerManager.WakeLock mWakeLock;
SparseIntArray mAlarmsPerUid = new SparseIntArray();
@@ -1784,40 +1783,37 @@ public class AlarmManagerService extends SystemService {
mMetricsHelper = new MetricsHelper(getContext(), mLock);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
- mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms();
mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms()
&& UserManager.supportsMultipleUsers();
if (mStartUserBeforeScheduledAlarms) {
mUserWakeupStore = new UserWakeupStore();
mUserWakeupStore.init();
}
- if (mUseFrozenStateToDropListenerAlarms) {
- final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> {
- final int size = frozenStates.length;
- if (uids.length != size) {
- Slog.wtf(TAG, "Got different length arrays in frozen state callback!"
- + " uids.length: " + uids.length + " frozenStates.length: " + size);
- // Cannot process received data in any meaningful way.
- return;
- }
- final IntArray affectedUids = new IntArray();
- for (int i = 0; i < size; i++) {
- if (frozenStates[i] != UID_FROZEN_STATE_FROZEN) {
- continue;
- }
- if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED,
- uids[i])) {
- continue;
- }
- affectedUids.add(uids[i]);
+ final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> {
+ final int size = frozenStates.length;
+ if (uids.length != size) {
+ Slog.wtf(TAG, "Got different length arrays in frozen state callback!"
+ + " uids.length: " + uids.length + " frozenStates.length: " + size);
+ // Cannot process received data in any meaningful way.
+ return;
+ }
+ final IntArray affectedUids = new IntArray();
+ for (int i = 0; i < size; i++) {
+ if (frozenStates[i] != UID_FROZEN_STATE_FROZEN) {
+ continue;
}
- if (affectedUids.size() > 0) {
- removeExactListenerAlarms(affectedUids.toArray());
+ if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED,
+ uids[i])) {
+ continue;
}
- };
- final ActivityManager am = getContext().getSystemService(ActivityManager.class);
- am.registerUidFrozenStateChangedCallback(new HandlerExecutor(mHandler), callback);
- }
+ affectedUids.add(uids[i]);
+ }
+ if (affectedUids.size() > 0) {
+ removeExactListenerAlarms(affectedUids.toArray());
+ }
+ };
+ final ActivityManager am = getContext().getSystemService(ActivityManager.class);
+ am.registerUidFrozenStateChangedCallback(new HandlerExecutor(mHandler), callback);
mListenerDeathRecipient = new IBinder.DeathRecipient() {
@Override
@@ -2994,13 +2990,10 @@ public class AlarmManagerService extends SystemService {
pw.println("Feature Flags:");
pw.increaseIndent();
- pw.print(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS,
- mUseFrozenStateToDropListenerAlarms);
- pw.println();
pw.print(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS,
Flags.startUserBeforeScheduledAlarms());
- pw.decreaseIndent();
pw.println();
+ pw.decreaseIndent();
pw.println();
pw.println("App Standby Parole: " + mAppStandbyParole);
@@ -5146,38 +5139,6 @@ public class AlarmManagerService extends SystemService {
removeForStoppedLocked(uid);
}
}
-
- @Override
- public void handleUidCachedChanged(int uid, boolean cached) {
- if (mUseFrozenStateToDropListenerAlarms) {
- // Use ActivityManager#UidFrozenStateChangedCallback instead.
- return;
- }
- if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, uid)) {
- return;
- }
- // Apps can quickly get frozen after being cached, breaking the exactness guarantee on
- // listener alarms. So going forward, the contract of exact listener alarms explicitly
- // states that they will be removed as soon as the app goes out of lifecycle. We still
- // allow a short grace period for quick shuffling of proc-states that may happen
- // unexpectedly when switching between different lifecycles and is generally hard for
- // apps to avoid.
-
- final long delay;
- synchronized (mLock) {
- delay = mConstants.CACHED_LISTENER_REMOVAL_DELAY;
- }
- final Integer uidObj = uid;
-
- if (cached && !mHandler.hasEqualMessages(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED,
- uidObj)) {
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED, uidObj),
- delay);
- } else {
- mHandler.removeEqualMessages(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED, uidObj);
- }
- }
};
private final BroadcastStats getStatsLocked(PendingIntent pi) {
diff --git a/core/api/current.txt b/core/api/current.txt
index 06cf9a5631ee..f66015841a4d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -23,6 +23,7 @@ package android {
field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
field public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
field public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
+ field @FlaggedApi("android.media.tv.flags.apply_picture_profiles") public static final String APPLY_PICTURE_PROFILE = "android.permission.APPLY_PICTURE_PROFILE";
field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS";
field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
@@ -336,7 +337,7 @@ package android {
field public static final String WRITE_SECURE_SETTINGS = "android.permission.WRITE_SECURE_SETTINGS";
field public static final String WRITE_SETTINGS = "android.permission.WRITE_SETTINGS";
field public static final String WRITE_SYNC_SETTINGS = "android.permission.WRITE_SYNC_SETTINGS";
- field @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final String WRITE_SYSTEM_PREFERENCES = "android.permission.WRITE_SYSTEM_PREFERENCES";
+ field @FlaggedApi("com.android.settingslib.flags.write_system_preference_permission_enabled") public static final String WRITE_SYSTEM_PREFERENCES = "android.permission.WRITE_SYSTEM_PREFERENCES";
field public static final String WRITE_VOICEMAIL = "com.android.voicemail.permission.WRITE_VOICEMAIL";
}
@@ -1276,6 +1277,7 @@ package android {
field public static final int paddingStart = 16843699; // 0x10103b3
field public static final int paddingTop = 16842967; // 0x10100d7
field public static final int paddingVertical = 16844094; // 0x101053e
+ field @FlaggedApi("android.content.pm.app_compat_option_16kb") public static final int pageSizeCompat;
field public static final int panelBackground = 16842846; // 0x101005e
field public static final int panelColorBackground = 16842849; // 0x1010061
field public static final int panelColorForeground = 16842848; // 0x1010060
@@ -13567,6 +13569,7 @@ package android.content.pm {
field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES";
field public static final String PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES = "android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES";
field public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE = "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
+ field @FlaggedApi("com.android.server.backup.enable_restricted_mode_changes") public static final String PROPERTY_USE_RESTRICTED_BACKUP_MODE = "android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE";
field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
field public static final int SIGNATURE_MATCH = 0; // 0x0
field public static final int SIGNATURE_NEITHER_SIGNED = 1; // 0x1
@@ -13803,6 +13806,7 @@ package android.content.pm {
public final class SharedLibraryInfo implements android.os.Parcelable {
method public int describeContents();
+ method @FlaggedApi("android.content.pm.sdk_dependency_installer") @NonNull public java.util.List<java.lang.String> getCertDigests();
method @NonNull public android.content.pm.VersionedPackage getDeclaringPackage();
method @NonNull public java.util.List<android.content.pm.VersionedPackage> getDependentPackages();
method @IntRange(from=0xffffffff) public long getLongVersion();
@@ -17470,6 +17474,7 @@ package android.graphics {
method public void setFloatUniform(@NonNull String, @NonNull float[]);
method public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter);
method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
+ method public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode);
method public void setIntUniform(@NonNull String, int);
method public void setIntUniform(@NonNull String, int, int);
method public void setIntUniform(@NonNull String, int, int, int);
@@ -17488,7 +17493,29 @@ package android.graphics {
method public void setFloatUniform(@NonNull String, float, float, float, float);
method public void setFloatUniform(@NonNull String, @NonNull float[]);
method public void setInputBuffer(@NonNull String, @NonNull android.graphics.BitmapShader);
+ method @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter);
method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
+ method @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode);
+ method public void setIntUniform(@NonNull String, int);
+ method public void setIntUniform(@NonNull String, int, int);
+ method public void setIntUniform(@NonNull String, int, int, int);
+ method public void setIntUniform(@NonNull String, int, int, int, int);
+ method public void setIntUniform(@NonNull String, @NonNull int[]);
+ }
+
+ @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public class RuntimeXfermode extends android.graphics.Xfermode {
+ ctor public RuntimeXfermode(@NonNull String);
+ method public void setColorUniform(@NonNull String, @ColorInt int);
+ method public void setColorUniform(@NonNull String, @ColorLong long);
+ method public void setColorUniform(@NonNull String, @NonNull android.graphics.Color);
+ method public void setFloatUniform(@NonNull String, float);
+ method public void setFloatUniform(@NonNull String, float, float);
+ method public void setFloatUniform(@NonNull String, float, float, float);
+ method public void setFloatUniform(@NonNull String, float, float, float, float);
+ method public void setFloatUniform(@NonNull String, @NonNull float[]);
+ method public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter);
+ method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
+ method public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode);
method public void setIntUniform(@NonNull String, int);
method public void setIntUniform(@NonNull String, int, int);
method public void setIntUniform(@NonNull String, int, int, int);
@@ -18826,6 +18853,19 @@ package android.hardware {
field public static final int TRANSFER_UNSPECIFIED = 0; // 0x0
}
+ @FlaggedApi("android.hardware.flags.luts_api") public final class DisplayLuts {
+ ctor @FlaggedApi("android.hardware.flags.luts_api") public DisplayLuts();
+ method @FlaggedApi("android.hardware.flags.luts_api") public void set(@NonNull android.hardware.DisplayLuts.Entry);
+ method @FlaggedApi("android.hardware.flags.luts_api") public void set(@NonNull android.hardware.DisplayLuts.Entry, @NonNull android.hardware.DisplayLuts.Entry);
+ }
+
+ @FlaggedApi("android.hardware.flags.luts_api") public static class DisplayLuts.Entry {
+ ctor @FlaggedApi("android.hardware.flags.luts_api") public DisplayLuts.Entry(@NonNull float[], int, int);
+ method @FlaggedApi("android.hardware.flags.luts_api") @NonNull public float[] getBuffer();
+ method @FlaggedApi("android.hardware.flags.luts_api") public int getDimension();
+ method @FlaggedApi("android.hardware.flags.luts_api") public int getSamplingKey();
+ }
+
public class GeomagneticField {
ctor public GeomagneticField(float, float, float, long);
method public float getDeclination();
@@ -18887,8 +18927,19 @@ package android.hardware {
field @FlaggedApi("android.media.codec.p210_format_support") public static final int YCBCR_P210 = 60; // 0x3c
}
+ @FlaggedApi("android.hardware.flags.luts_api") public final class LutProperties {
+ method @FlaggedApi("android.hardware.flags.luts_api") public int getDimension();
+ method @FlaggedApi("android.hardware.flags.luts_api") @NonNull public int[] getSamplingKeys();
+ method @FlaggedApi("android.hardware.flags.luts_api") public int getSize();
+ field @FlaggedApi("android.hardware.flags.luts_api") public static final int ONE_DIMENSION = 1; // 0x1
+ field @FlaggedApi("android.hardware.flags.luts_api") public static final int SAMPLING_KEY_MAX_RGB = 1; // 0x1
+ field @FlaggedApi("android.hardware.flags.luts_api") public static final int SAMPLING_KEY_RGB = 0; // 0x0
+ field @FlaggedApi("android.hardware.flags.luts_api") public static final int THREE_DIMENSION = 3; // 0x3
+ }
+
@FlaggedApi("android.hardware.flags.overlayproperties_class_api") public final class OverlayProperties implements android.os.Parcelable {
method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public int describeContents();
+ method @FlaggedApi("android.hardware.flags.luts_api") @NonNull public android.hardware.LutProperties[] getLutProperties();
method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public boolean isCombinationSupported(int, int);
method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public boolean isMixedColorSpacesSupported();
method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -21982,7 +22033,7 @@ package android.media {
public final class AudioPlaybackConfiguration implements android.os.Parcelable {
method public int describeContents();
method public android.media.AudioAttributes getAudioAttributes();
- method @Nullable public android.media.AudioDeviceInfo getAudioDeviceInfo();
+ method @Deprecated @FlaggedApi("android.media.audio.routed_device_ids") @Nullable public android.media.AudioDeviceInfo getAudioDeviceInfo();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioPlaybackConfiguration> CREATOR;
}
@@ -22065,6 +22116,7 @@ package android.media {
method public android.media.AudioDeviceInfo getPreferredDevice();
method public int getRecordingState();
method public android.media.AudioDeviceInfo getRoutedDevice();
+ method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public java.util.List<android.media.AudioDeviceInfo> getRoutedDevices();
method public int getSampleRate();
method public int getState();
method public int getTimestamp(@NonNull android.media.AudioTimestamp, int);
@@ -22159,6 +22211,7 @@ package android.media {
method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public android.media.AudioDeviceInfo getPreferredDevice();
method public android.media.AudioDeviceInfo getRoutedDevice();
+ method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public default java.util.List<android.media.AudioDeviceInfo> getRoutedDevices();
method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
}
@@ -22217,6 +22270,7 @@ package android.media {
method public int getPositionNotificationPeriod();
method public android.media.AudioDeviceInfo getPreferredDevice();
method public android.media.AudioDeviceInfo getRoutedDevice();
+ method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public java.util.List<android.media.AudioDeviceInfo> getRoutedDevices();
method public int getSampleRate();
method @IntRange(from=1) public int getStartThresholdInFrames();
method public int getState();
@@ -24385,6 +24439,7 @@ package android.media {
method @NonNull public android.media.PlaybackParams getPlaybackParams();
method public android.media.AudioDeviceInfo getPreferredDevice();
method public android.media.AudioDeviceInfo getRoutedDevice();
+ method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public java.util.List<android.media.AudioDeviceInfo> getRoutedDevices();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
method @NonNull public android.media.SyncParams getSyncParams();
method @Nullable public android.media.MediaTimestamp getTimestamp();
@@ -24598,6 +24653,7 @@ package android.media {
method public android.os.PersistableBundle getMetrics();
method public android.media.AudioDeviceInfo getPreferredDevice();
method public android.media.AudioDeviceInfo getRoutedDevice();
+ method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public java.util.List<android.media.AudioDeviceInfo> getRoutedDevices();
method public android.view.Surface getSurface();
method public boolean isPrivacySensitive();
method public void pause() throws java.lang.IllegalStateException;
@@ -28529,6 +28585,8 @@ package android.media.tv.ad {
method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.TvAdCallback);
method public void setOnUnhandledInputEventListener(@NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
method public boolean setTvView(@Nullable android.media.tv.TvView);
+ method @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public void setZOrderMediaOverlay(boolean);
+ method @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public void setZOrderOnTop(boolean);
method public void startAdService();
method public void stopAdService();
field public static final String ERROR_KEY_ERROR_CODE = "error_code";
@@ -28801,6 +28859,8 @@ package android.media.tv.interactive {
method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.interactive.TvInteractiveAppView.OnUnhandledInputEventListener);
method public void setTeletextAppEnabled(boolean);
method public int setTvView(@Nullable android.media.tv.TvView);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void setZOrderMediaOverlay(boolean);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void setZOrderOnTop(boolean);
method public void startInteractiveApp();
method public void stopInteractiveApp();
field public static final String BI_INTERACTIVE_APP_KEY_ALIAS = "alias";
@@ -29840,6 +29900,7 @@ package android.net.vcn {
method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
method public boolean hasGatewayOption(int);
method @FlaggedApi("android.net.vcn.safe_mode_config") public boolean isSafeModeEnabled();
+ field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET = -1; // 0xffffffff
field public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; // 0x0
}
@@ -33313,6 +33374,14 @@ package android.os {
method public final android.os.CountDownTimer start();
}
+ @FlaggedApi("android.os.cpu_gpu_headrooms") public final class CpuHeadroomParams {
+ ctor public CpuHeadroomParams();
+ method public int getCalculationType();
+ method public void setCalculationType(int);
+ field public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1
+ field public static final int CPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0
+ }
+
public final class CpuUsageInfo implements android.os.Parcelable {
method public int describeContents();
method public long getActive();
@@ -33560,6 +33629,14 @@ package android.os {
method public void onProgress(long);
}
+ @FlaggedApi("android.os.cpu_gpu_headrooms") public final class GpuHeadroomParams {
+ ctor public GpuHeadroomParams();
+ method public int getCalculationType();
+ method public void setCalculationType(int);
+ field public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1
+ field public static final int GPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0
+ }
+
public class Handler {
ctor @Deprecated public Handler();
ctor @Deprecated public Handler(@Nullable android.os.Handler.Callback);
@@ -34810,6 +34887,10 @@ package android.os.health {
}
public class SystemHealthManager {
+ method @FlaggedApi("android.os.cpu_gpu_headrooms") @FloatRange(from=0.0f, to=100.0f) public float getCpuHeadroom(@Nullable android.os.CpuHeadroomParams);
+ method @FlaggedApi("android.os.cpu_gpu_headrooms") public long getCpuHeadroomMinIntervalMillis();
+ method @FlaggedApi("android.os.cpu_gpu_headrooms") @FloatRange(from=0.0f, to=100.0f) public float getGpuHeadroom(@Nullable android.os.GpuHeadroomParams);
+ method @FlaggedApi("android.os.cpu_gpu_headrooms") public long getGpuHeadroomMinIntervalMillis();
method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getPowerMonitorReadings(@NonNull java.util.List<android.os.PowerMonitor>, @Nullable java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PowerMonitorReadings,java.lang.RuntimeException>);
method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getSupportedPowerMonitors(@Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.os.PowerMonitor>>);
method public android.os.health.HealthStats takeMyUidSnapshot();
@@ -40681,6 +40762,7 @@ package android.service.autofill {
method public int describeContents();
method @Deprecated @Nullable public android.os.Bundle getClientState();
method @Nullable public java.util.List<android.service.autofill.FillEventHistory.Event> getEvents();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getSessionId();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillEventHistory> CREATOR;
}
@@ -40690,10 +40772,12 @@ package android.service.autofill {
method @Nullable public android.os.Bundle getClientState();
method @Nullable public String getDatasetId();
method @NonNull public java.util.Map<android.view.autofill.AutofillId,android.service.autofill.FieldClassification> getFieldsClassification();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") @Nullable public android.view.autofill.AutofillId getFocusedId();
method @NonNull public java.util.Set<java.lang.String> getIgnoredDatasetIds();
method @NonNull public java.util.Map<android.view.autofill.AutofillId,java.util.Set<java.lang.String>> getManuallyEnteredField();
method public int getNoSaveUiReason();
method @NonNull public java.util.Set<java.lang.String> getSelectedDatasetIds();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") @NonNull public java.util.Set<java.lang.String> getShownDatasetIds();
method public int getType();
method public int getUiType();
field public static final int NO_SAVE_UI_REASON_DATASET_MATCH = 6; // 0x6
@@ -40702,6 +40786,7 @@ package android.service.autofill {
field public static final int NO_SAVE_UI_REASON_NONE = 0; // 0x0
field public static final int NO_SAVE_UI_REASON_NO_SAVE_INFO = 1; // 0x1
field public static final int NO_SAVE_UI_REASON_NO_VALUE_CHANGED = 4; // 0x4
+ field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final int NO_SAVE_UI_REASON_USING_CREDMAN = 7; // 0x7
field public static final int NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG = 2; // 0x2
field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
field public static final int TYPE_CONTEXT_COMMITTED = 4; // 0x4
@@ -40710,6 +40795,7 @@ package android.service.autofill {
field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
field public static final int TYPE_VIEW_REQUESTED_AUTOFILL = 6; // 0x6
+ field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final int UI_TYPE_CREDMAN = 4; // 0x4
field public static final int UI_TYPE_DIALOG = 3; // 0x3
field public static final int UI_TYPE_INLINE = 2; // 0x2
field public static final int UI_TYPE_MENU = 1; // 0x1
@@ -41995,6 +42081,7 @@ package android.service.quickaccesswallet {
public abstract class QuickAccessWalletService extends android.app.Service {
ctor public QuickAccessWalletService();
+ method @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") @Nullable public android.app.PendingIntent getGestureTargetActivityPendingIntent();
method @Nullable public android.app.PendingIntent getTargetActivityPendingIntent();
method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onWalletCardSelected(@NonNull android.service.quickaccesswallet.SelectWalletCardRequest);
@@ -46672,6 +46759,8 @@ package android.telephony {
method public long getDataUsageBytes();
method public long getDataUsageTime();
method @NonNull public int[] getNetworkTypes();
+ method @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") @Nullable public java.time.ZonedDateTime getPlanEndDate();
+ method @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public int getSubscriptionStatus();
method @Nullable public CharSequence getSummary();
method @Nullable public CharSequence getTitle();
method public void writeToParcel(android.os.Parcel, int);
@@ -46682,6 +46771,11 @@ package android.telephony {
field public static final int LIMIT_BEHAVIOR_DISABLED = 0; // 0x0
field public static final int LIMIT_BEHAVIOR_THROTTLED = 2; // 0x2
field public static final int LIMIT_BEHAVIOR_UNKNOWN = -1; // 0xffffffff
+ field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_ACTIVE = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_INACTIVE = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_SUSPENDED = 4; // 0x4
+ field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_TRIAL = 3; // 0x3
+ field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_UNKNOWN = 0; // 0x0
field public static final long TIME_UNKNOWN = -1L; // 0xffffffffffffffffL
}
@@ -46693,6 +46787,7 @@ package android.telephony {
method public android.telephony.SubscriptionPlan.Builder setDataLimit(long, int);
method public android.telephony.SubscriptionPlan.Builder setDataUsage(long, long);
method @NonNull public android.telephony.SubscriptionPlan.Builder setNetworkTypes(@NonNull int[]);
+ method @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") @NonNull public android.telephony.SubscriptionPlan.Builder setSubscriptionStatus(int);
method public android.telephony.SubscriptionPlan.Builder setSummary(@Nullable CharSequence);
method public android.telephony.SubscriptionPlan.Builder setTitle(@Nullable CharSequence);
}
@@ -49672,6 +49767,14 @@ package android.text.style {
method public abstract void updateMeasureState(@NonNull android.text.TextPaint);
}
+ @FlaggedApi("android.view.inputmethod.writing_tools") public final class NoWritingToolsSpan implements android.text.ParcelableSpan {
+ ctor public NoWritingToolsSpan();
+ method public int describeContents();
+ method public int getSpanTypeId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.text.style.NoWritingToolsSpan> CREATOR;
+ }
+
public interface ParagraphStyle {
}
@@ -53168,6 +53271,7 @@ package android.view {
method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction setFrameTimeline(long);
method @Deprecated @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
+ method @FlaggedApi("android.hardware.flags.luts_api") @NonNull public android.view.SurfaceControl.Transaction setLuts(@NonNull android.view.SurfaceControl, @Nullable android.hardware.DisplayLuts);
method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean);
method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float);
method @NonNull public android.view.SurfaceControl.Transaction setScale(@NonNull android.view.SurfaceControl, float, float);
@@ -54842,6 +54946,8 @@ package android.view {
method public abstract void setTransformation(android.graphics.Matrix);
method public abstract void setVisibility(int);
method public abstract void setWebDomain(@Nullable String);
+ field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE";
+ field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
}
public abstract static class ViewStructure.HtmlInfo {
@@ -56564,6 +56670,11 @@ package android.view.autofill {
public final class AutofillId implements android.os.Parcelable {
method @NonNull public static android.view.autofill.AutofillId create(@NonNull android.view.View, int);
method public int describeContents();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getAutofillVirtualId();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getSessionId();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getViewId();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") public boolean isInAutofillSession();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") public boolean isVirtual();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.autofill.AutofillId> CREATOR;
}
@@ -56960,6 +57071,7 @@ package android.view.inputmethod {
method @NonNull public java.util.Set<java.lang.Class<? extends android.view.inputmethod.PreviewableHandwritingGesture>> getSupportedHandwritingGesturePreviews();
method @NonNull public java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>> getSupportedHandwritingGestures();
method @FlaggedApi("android.view.inputmethod.editorinfo_handwriting_enabled") public boolean isStylusHandwritingEnabled();
+ method @FlaggedApi("android.view.inputmethod.writing_tools") public boolean isWritingToolsEnabled();
method public final void makeCompatible(int);
method @FlaggedApi("android.view.inputmethod.public_autofill_id_in_editorinfo") public void setAutofillId(@Nullable android.view.autofill.AutofillId);
method public void setInitialSurroundingSubText(@NonNull CharSequence, int);
@@ -56968,6 +57080,7 @@ package android.view.inputmethod {
method @FlaggedApi("android.view.inputmethod.editorinfo_handwriting_enabled") public void setStylusHandwritingEnabled(boolean);
method public void setSupportedHandwritingGesturePreviews(@NonNull java.util.Set<java.lang.Class<? extends android.view.inputmethod.PreviewableHandwritingGesture>>);
method public void setSupportedHandwritingGestures(@NonNull java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>>);
+ method @FlaggedApi("android.view.inputmethod.writing_tools") public void setWritingToolsEnabled(boolean);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.EditorInfo> CREATOR;
field public static final int IME_ACTION_DONE = 6; // 0x6
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index 4ada53e1bf34..ad5bd31828e0 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -1,4 +1,10 @@
// Baseline format: 1.0
+ActionValue: android.view.ViewStructure#EXTRA_VIRTUAL_STRUCTURE_TYPE:
+ Inconsistent extra value; expected `android.view.extra.VIRTUAL_STRUCTURE_TYPE`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE`
+ActionValue: android.view.ViewStructure#EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER:
+ Inconsistent extra value; expected `android.view.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`
+
+
BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior
BroadcastBehavior: android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED:
@@ -1185,6 +1191,10 @@ UnflaggedApi: android.R.dimen#system_corner_radius_xlarge:
New API must be flagged with @FlaggedApi: field android.R.dimen.system_corner_radius_xlarge
UnflaggedApi: android.R.dimen#system_corner_radius_xsmall:
New API must be flagged with @FlaggedApi: field android.R.dimen.system_corner_radius_xsmall
+UnflaggedApi: android.R.integer#status_bar_notification_info_maxnum:
+ Changes from not deprecated to deprecated must be flagged with @FlaggedApi: field android.R.integer.status_bar_notification_info_maxnum
+UnflaggedApi: android.R.string#status_bar_notification_info_overflow:
+ Changes from not deprecated to deprecated must be flagged with @FlaggedApi: field android.R.string.status_bar_notification_info_overflow
UnflaggedApi: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INTERNAL_ERROR:
New API must be flagged with @FlaggedApi: field android.accessibilityservice.AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR
UnflaggedApi: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INVALID:
@@ -1477,7 +1487,6 @@ UnflaggedApi: android.graphics.text.PositionedGlyphs#getItalicOverride(int):
New API must be flagged with @FlaggedApi: method android.graphics.text.PositionedGlyphs.getItalicOverride(int)
UnflaggedApi: android.graphics.text.PositionedGlyphs#getWeightOverride(int):
New API must be flagged with @FlaggedApi: method android.graphics.text.PositionedGlyphs.getWeightOverride(int)
-
UnflaggedApi: android.media.MediaRoute2Info#TYPE_REMOTE_CAR:
New API must be flagged with @FlaggedApi: field android.media.MediaRoute2Info.TYPE_REMOTE_CAR
UnflaggedApi: android.media.MediaRoute2Info#TYPE_REMOTE_COMPUTER:
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index bc73220cb4c1..1a949d84c052 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -251,6 +251,10 @@ package android.media.session {
package android.net {
+ @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class ConnectivityFrameworkInitializerBaklava {
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static void registerServiceWrappers();
+ }
+
public class LocalSocket implements java.io.Closeable {
ctor public LocalSocket(@NonNull java.io.FileDescriptor);
}
@@ -310,6 +314,25 @@ package android.net.netstats {
}
+package android.net.vcn {
+
+ @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class VcnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int describeContents();
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public long getApplicableRedactions();
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int getMinUdpPort4500NatTimeoutSeconds();
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.TransportInfo makeCopy(long);
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnTransportInfo> CREATOR;
+ }
+
+ @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final class VcnTransportInfo.Builder {
+ ctor @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public VcnTransportInfo.Builder();
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo build();
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
+ }
+
+}
+
package android.net.wifi {
public final class WifiMigration {
@@ -379,6 +402,13 @@ package android.os {
field public static final int DEVICE_INITIAL_SDK_INT;
}
+ public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
+ method @FlaggedApi("android.os.enable_has_binders") public int hasBinders();
+ field @FlaggedApi("android.os.enable_has_binders") public static final int STATUS_BINDERS_NOT_PRESENT = 0; // 0x0
+ field @FlaggedApi("android.os.enable_has_binders") public static final int STATUS_BINDERS_PRESENT = 1; // 0x1
+ field @FlaggedApi("android.os.enable_has_binders") public static final int STATUS_BINDERS_UNKNOWN = 2; // 0x2
+ }
+
public class Handler {
method @FlaggedApi("android.os.mainline_vcn_platform_api") public final boolean hasMessagesOrCallbacks();
method @FlaggedApi("android.os.mainline_vcn_platform_api") public final void removeCallbacksAndEqualMessages(@Nullable Object);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ed95fdd52f45..9197a0129593 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -57,6 +57,7 @@ package android {
field @Deprecated public static final String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE";
field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE";
field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE";
+ field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final String BIND_DEPENDENCY_INSTALLER = "android.permission.BIND_DEPENDENCY_INSTALLER";
field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
field public static final String BIND_DISPLAY_HASHING_SERVICE = "android.permission.BIND_DISPLAY_HASHING_SERVICE";
field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String BIND_DOMAIN_SELECTION_SERVICE = "android.permission.BIND_DOMAIN_SELECTION_SERVICE";
@@ -65,6 +66,7 @@ package android {
field @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") public static final String BIND_EXPLICIT_HEALTH_CHECK_SERVICE = "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE";
field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE";
field public static final String BIND_FIELD_CLASSIFICATION_SERVICE = "android.permission.BIND_FIELD_CLASSIFICATION_SERVICE";
+ field @FlaggedApi("android.security.afl_api") public static final String BIND_FORENSIC_EVENT_TRANSPORT_SERVICE = "android.permission.BIND_FORENSIC_EVENT_TRANSPORT_SERVICE";
field public static final String BIND_GBA_SERVICE = "android.permission.BIND_GBA_SERVICE";
field public static final String BIND_HOTWORD_DETECTION_SERVICE = "android.permission.BIND_HOTWORD_DETECTION_SERVICE";
field public static final String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE";
@@ -164,6 +166,7 @@ package android {
field public static final String HDMI_CEC = "android.permission.HDMI_CEC";
field @Deprecated public static final String HIDE_NON_SYSTEM_OVERLAY_WINDOWS = "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS";
field public static final String INJECT_EVENTS = "android.permission.INJECT_EVENTS";
+ field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final String INSTALL_DEPENDENCY_SHARED_LIBRARIES = "android.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES";
field public static final String INSTALL_DPC_PACKAGES = "android.permission.INSTALL_DPC_PACKAGES";
field public static final String INSTALL_DYNAMIC_SYSTEM = "android.permission.INSTALL_DYNAMIC_SYSTEM";
field public static final String INSTALL_EXISTING_PACKAGES = "com.android.permission.INSTALL_EXISTING_PACKAGES";
@@ -209,6 +212,7 @@ package android {
field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES";
field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS";
field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
+ field @FlaggedApi("android.security.afl_api") public static final String MANAGE_FORENSIC_STATE = "android.permission.MANAGE_FORENSIC_STATE";
field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY";
field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE";
field @FlaggedApi("android.media.tv.flags.media_quality_fw") public static final String MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE = "android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE";
@@ -304,6 +308,7 @@ package android {
field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS";
field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG";
field public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
+ field @FlaggedApi("android.security.afl_api") public static final String READ_FORENSIC_STATE = "android.permission.READ_FORENSIC_STATE";
field public static final String READ_GLOBAL_APP_SEARCH_DATA = "android.permission.READ_GLOBAL_APP_SEARCH_DATA";
field @FlaggedApi("android.content.pm.get_resolved_apk_path") public static final String READ_INSTALLED_SESSION_PATHS = "android.permission.READ_INSTALLED_SESSION_PATHS";
field public static final String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS";
@@ -528,6 +533,7 @@ package android {
field public static final int config_systemCallStreaming = 17039431; // 0x1040047
field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039
field public static final int config_systemContacts = 17039403; // 0x104002b
+ field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final int config_systemDependencyInstaller;
field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046
field public static final int config_systemGallery = 17039399; // 0x1040027
field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035
@@ -1336,8 +1342,10 @@ package android.app.admin {
public class DevicePolicyManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback();
- method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
+ method @Deprecated @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
+ method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
+ method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeCreateManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams, @NonNull android.os.UserHandle) throws android.app.admin.ProvisioningException;
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public java.util.Set<java.lang.Integer> getApplicationExemptions(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
@@ -1932,6 +1940,7 @@ package android.app.backup {
method public android.os.IBinder getBinder();
method public long getCurrentRestoreSet();
method public int getNextFullRestoreDataChunk(android.os.ParcelFileDescriptor);
+ method @FlaggedApi("com.android.server.backup.enable_restricted_mode_changes") @NonNull public java.util.List<java.lang.String> getPackagesThatShouldNotUseRestrictedMode(@NonNull java.util.List<java.lang.String>, int);
method public int getRestoreData(android.os.ParcelFileDescriptor);
method public int getTransportFlags();
method public int initializeDevice();
@@ -4585,6 +4594,24 @@ package android.content.pm {
}
+package android.content.pm.dependencyinstaller {
+
+ @FlaggedApi("android.content.pm.sdk_dependency_installer") public final class DependencyInstallerCallback implements android.os.Parcelable {
+ method public int describeContents();
+ method public void onAllDependenciesResolved(@NonNull int[]);
+ method public void onFailureToResolveAllDependencies();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.dependencyinstaller.DependencyInstallerCallback> CREATOR;
+ }
+
+ @FlaggedApi("android.content.pm.sdk_dependency_installer") public abstract class DependencyInstallerService extends android.app.Service {
+ ctor public DependencyInstallerService();
+ method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
+ method public abstract void onDependenciesRequired(@NonNull java.util.List<android.content.pm.SharedLibraryInfo>, @NonNull android.content.pm.dependencyinstaller.DependencyInstallerCallback);
+ }
+
+}
+
package android.content.pm.dex {
public class ArtManager {
@@ -5045,15 +5072,27 @@ package android.hardware.biometrics {
package android.hardware.camera2 {
+ public final class CameraCharacteristics extends android.hardware.camera2.CameraMetadata<android.hardware.camera2.CameraCharacteristics.Key<?>> {
+ field @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.SharedSessionConfiguration> SHARED_SESSION_CONFIGURATION;
+ }
+
public abstract class CameraDevice implements java.lang.AutoCloseable {
method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1
field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0
+ field @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public static final int SESSION_OPERATION_MODE_SHARED = 2; // 0x2
field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000
}
+ public abstract static class CameraDevice.StateCallback {
+ method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public void onClientSharedAccessPriorityChanged(@NonNull android.hardware.camera2.CameraDevice, boolean);
+ method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public void onOpenedInSharedMode(@NonNull android.hardware.camera2.CameraDevice, boolean);
+ }
+
public final class CameraManager {
+ method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public boolean isCameraDeviceSharingSupported(@NonNull String) throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
+ method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openSharedCamera(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
}
public abstract static class CameraManager.AvailabilityCallback {
@@ -5061,6 +5100,12 @@ package android.hardware.camera2 {
method @RequiresPermission(android.Manifest.permission.CAMERA_OPEN_CLOSE_LISTENER) public void onCameraOpened(@NonNull String, @NonNull String);
}
+ @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public abstract class CameraSharedCaptureSession extends android.hardware.camera2.CameraCaptureSession {
+ ctor public CameraSharedCaptureSession();
+ method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public abstract int startStreaming(@NonNull java.util.List<android.view.Surface>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraCaptureSession.CaptureCallback) throws android.hardware.camera2.CameraAccessException;
+ method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public abstract void stopStreaming() throws android.hardware.camera2.CameraAccessException;
+ }
+
}
package android.hardware.camera2.extension {
@@ -5169,6 +5214,83 @@ package android.hardware.camera2.params {
field public static final int ROTATION_90 = 1; // 0x1
}
+ public final class SessionConfiguration implements android.os.Parcelable {
+ field @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public static final int SESSION_SHARED = 2; // 0x2
+ }
+
+ @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public final class SharedSessionConfiguration {
+ method @Nullable public android.graphics.ColorSpace getColorSpace();
+ method @NonNull public java.util.List<android.hardware.camera2.params.SharedSessionConfiguration.SharedOutputConfiguration> getOutputStreamsInformation();
+ }
+
+ public static final class SharedSessionConfiguration.SharedOutputConfiguration {
+ method public int getDataspace();
+ method public int getFormat();
+ method public int getMirrorMode();
+ method @Nullable public String getPhysicalCameraId();
+ method @NonNull public android.util.Size getSize();
+ method public long getStreamUseCase();
+ method public int getSurfaceType();
+ method public int getTimestampBase();
+ method public long getUsage();
+ method public boolean isReadoutTimestampEnabled();
+ }
+
+}
+
+package android.hardware.contexthub {
+
+ @FlaggedApi("android.chre.flags.offload_api") public class HubDiscoveryInfo {
+ method @NonNull public android.hardware.contexthub.HubEndpointInfo getHubEndpointInfo();
+ }
+
+ @FlaggedApi("android.chre.flags.offload_api") public class HubEndpoint {
+ method @Nullable public android.hardware.contexthub.IHubEndpointLifecycleCallback getLifecycleCallback();
+ method @Nullable public String getTag();
+ }
+
+ public static final class HubEndpoint.Builder {
+ ctor public HubEndpoint.Builder(@NonNull android.content.Context);
+ method @NonNull public android.hardware.contexthub.HubEndpoint build();
+ method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback);
+ method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback);
+ method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setTag(@NonNull String);
+ }
+
+ @FlaggedApi("android.chre.flags.offload_api") public final class HubEndpointInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier getIdentifier();
+ method @NonNull public String getName();
+ method @Nullable public String getTag();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.contexthub.HubEndpointInfo> CREATOR;
+ }
+
+ public static class HubEndpointInfo.HubEndpointIdentifier {
+ method public long getEndpoint();
+ method public long getHub();
+ }
+
+ @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable {
+ method public void close();
+ }
+
+ @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSessionResult {
+ method @NonNull public static android.hardware.contexthub.HubEndpointSessionResult accept();
+ method @Nullable public String getReason();
+ method public boolean isAccepted();
+ method @NonNull public static android.hardware.contexthub.HubEndpointSessionResult reject(@NonNull String);
+ }
+
+ @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
+ method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
+ method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo);
+ method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
+ field public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4; // 0x4
+ field public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3; // 0x3
+ field public static final int REASON_UNSPECIFIED = 0; // 0x0
+ }
+
}
package android.hardware.devicestate {
@@ -5301,6 +5423,7 @@ package android.hardware.display {
method @Nullable @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getDefaultBrightnessConfiguration();
method public android.util.Pair<float[],float[]> getMinimumBrightnessCurve();
method public android.graphics.Point getStableDisplaySize();
+ method @FlaggedApi("com.android.server.display.feature.flags.is_always_on_available_api") public boolean isAlwaysOnDisplayCurrentlyAvailable();
method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float);
@@ -6162,6 +6285,7 @@ package android.hardware.location {
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubClient createClient(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.app.PendingIntent, long);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> disableNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> enableNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
+ method @FlaggedApi("android.chre.flags.offload_api") @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public java.util.List<android.hardware.contexthub.HubDiscoveryInfo> findEndpoints(long);
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int[] findNanoAppOnHub(int, @NonNull android.hardware.location.NanoAppFilter);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int[] getContextHubHandles();
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubInfo getContextHubInfo(int);
@@ -6169,13 +6293,16 @@ package android.hardware.location {
method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.NanoAppInstanceInfo getNanoAppInstanceInfo(int);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int loadNanoApp(int, @NonNull android.hardware.location.NanoApp);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> loadNanoApp(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.NanoAppBinary);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.util.List<android.hardware.location.NanoAppState>> queryNanoApps(@NonNull android.hardware.location.ContextHubInfo);
method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessage(int, int, @NonNull android.hardware.location.ContextHubMessage);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int unloadNanoApp(int);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
field public static final int AUTHORIZATION_DENIED = 0; // 0x0
field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1
field public static final int AUTHORIZATION_GRANTED = 2; // 0x2
@@ -7464,6 +7591,7 @@ package android.media {
}
public final class AudioPlaybackConfiguration implements android.os.Parcelable {
+ method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceInfo> getAudioDeviceInfos();
method public int getChannelMask();
method public int getClientPid();
method public int getClientUid();
@@ -7618,7 +7746,7 @@ package android.media {
public final class MediaCas implements java.lang.AutoCloseable {
method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceHolderRetain(boolean);
- method @FlaggedApi("com.android.media.flags.update_client_profile_priority") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean updateResourcePriority(int, int);
+ method @FlaggedApi("android.media.tv.flags.mediacas_update_client_profile_priority") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean updateResourcePriority(int, int);
}
public final class MediaCodec {
@@ -12570,6 +12698,29 @@ package android.security.advancedprotection {
}
+package android.security.forensic {
+
+ @FlaggedApi("android.security.afl_api") public class ForensicManager {
+ method @RequiresPermission(android.Manifest.permission.READ_FORENSIC_STATE) public void addStateCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_FORENSIC_STATE) public void disable(@NonNull java.util.concurrent.Executor, @NonNull android.security.forensic.ForensicManager.CommandCallback);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_FORENSIC_STATE) public void enable(@NonNull java.util.concurrent.Executor, @NonNull android.security.forensic.ForensicManager.CommandCallback);
+ method @RequiresPermission(android.Manifest.permission.READ_FORENSIC_STATE) public void removeStateCallback(@NonNull java.util.function.Consumer<java.lang.Integer>);
+ field public static final int ERROR_DATA_SOURCE_UNAVAILABLE = 4; // 0x4
+ field public static final int ERROR_PERMISSION_DENIED = 1; // 0x1
+ field public static final int ERROR_TRANSPORT_UNAVAILABLE = 3; // 0x3
+ field public static final int ERROR_UNKNOWN = 0; // 0x0
+ field public static final int STATE_DISABLED = 1; // 0x1
+ field public static final int STATE_ENABLED = 2; // 0x2
+ field public static final int STATE_UNKNOWN = 0; // 0x0
+ }
+
+ public static interface ForensicManager.CommandCallback {
+ method public void onFailure(int);
+ method public void onSuccess();
+ }
+
+}
+
package android.security.keystore {
public class AndroidKeyStoreProvider extends java.security.Provider {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 119271390b94..8fd2cd55b8a8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2318,7 +2318,7 @@ package android.net.wifi.sharedconnectivity.app {
public class SharedConnectivityManager {
method @Nullable public static android.net.wifi.sharedconnectivity.app.SharedConnectivityManager create(@NonNull android.content.Context, @NonNull String, @NonNull String);
- method @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api") @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
+ method @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
method @Nullable public android.content.ServiceConnection getServiceConnection();
method public void setService(@Nullable android.os.IInterface);
}
@@ -3240,6 +3240,7 @@ package android.service.quickaccesswallet {
method @Nullable public android.content.Intent createWalletIntent();
method @Nullable public android.content.Intent createWalletSettingsIntent();
method public void disconnect();
+ method @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") public void getGestureTargetActivityPendingIntent(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.GesturePendingIntentCallback);
method public void getWalletCards(@NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback);
method public void getWalletCards(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback);
method public void getWalletPendingIntent(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.WalletPendingIntentCallback);
@@ -3251,6 +3252,10 @@ package android.service.quickaccesswallet {
method public void selectWalletCard(@NonNull android.service.quickaccesswallet.SelectWalletCardRequest);
}
+ @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") public static interface QuickAccessWalletClient.GesturePendingIntentCallback {
+ method @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") public void onGesturePendingIntentRetrieved(@Nullable android.app.PendingIntent);
+ }
+
public static interface QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
method public void onWalletCardRetrievalError(@NonNull android.service.quickaccesswallet.GetWalletCardsError);
method public void onWalletCardsRetrieved(@NonNull android.service.quickaccesswallet.GetWalletCardsResponse);
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 08bb08254476..c2fac70d1f68 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -1,4 +1,10 @@
// Baseline format: 1.0
+ActionValue: android.view.contentcapture.ViewNode.ViewStructureImpl#EXTRA_VIRTUAL_STRUCTURE_TYPE:
+ Inconsistent extra value; expected `android.view.contentcapture.extra.VIRTUAL_STRUCTURE_TYPE`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE`
+ActionValue: android.view.contentcapture.ViewNode.ViewStructureImpl#EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER:
+ Inconsistent extra value; expected `android.view.contentcapture.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`
+
+
BannedThrow: android.os.vibrator.persistence.VibrationXmlSerializer#serialize(android.os.VibrationEffect, java.io.Writer):
Methods must not throw unchecked exceptions
diff --git a/core/java/Android.bp b/core/java/Android.bp
index cf5ebbaa37b4..bc38294279a8 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -232,6 +232,8 @@ aidl_interface {
"android.hardware.power-aidl",
],
srcs: [
+ "android/os/CpuHeadroomParamsInternal.aidl",
+ "android/os/GpuHeadroomParamsInternal.aidl",
"android/os/IHintManager.aidl",
"android/os/IHintSession.aidl",
],
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 8b37dbd04bec..ce0ec602e612 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1624,9 +1624,19 @@ public class AppOpsManager {
/** @hide Access to read oxygen saturation. */
public static final int OP_READ_OXYGEN_SATURATION = AppOpEnums.APP_OP_READ_OXYGEN_SATURATION;
+ /** @hide Access to write system preferences. */
+ public static final int OP_WRITE_SYSTEM_PREFERENCES =
+ AppOpEnums.APP_OP_WRITE_SYSTEM_PREFERENCES;
+
+ /** @hide Access to audio playback and control APIs. */
+ public static final int OP_CONTROL_AUDIO = AppOpEnums.APP_OP_CONTROL_AUDIO;
+
+ /** @hide Similar to {@link OP_CONTROL_AUDIO}, but doesn't require capabilities. */
+ public static final int OP_CONTROL_AUDIO_PARTIAL = AppOpEnums.APP_OP_CONTROL_AUDIO_PARTIAL;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 153;
+ public static final int _NUM_OP = 156;
/**
* All app ops represented as strings.
@@ -1783,6 +1793,9 @@ public class AppOpsManager {
OPSTR_READ_SKIN_TEMPERATURE,
OPSTR_RANGING,
OPSTR_READ_OXYGEN_SATURATION,
+ OPSTR_WRITE_SYSTEM_PREFERENCES,
+ OPSTR_CONTROL_AUDIO,
+ OPSTR_CONTROL_AUDIO_PARTIAL,
})
public @interface AppOpString {}
@@ -2540,6 +2553,15 @@ public class AppOpsManager {
@FlaggedApi(Flags.FLAG_RANGING_PERMISSION_ENABLED)
public static final String OPSTR_RANGING = "android:ranging";
+ /** @hide Access to system preferences write services */
+ public static final String OPSTR_WRITE_SYSTEM_PREFERENCES = "android:write_system_preferences";
+
+ /** @hide Access to audio playback and control APIs */
+ public static final String OPSTR_CONTROL_AUDIO = "android:control_audio";
+
+ /** @hide Access to a audio playback and control APIs without capability requirements */
+ public static final String OPSTR_CONTROL_AUDIO_PARTIAL = "android:control_audio_partial";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2656,6 +2678,7 @@ public class AppOpsManager {
OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
OP_MEDIA_ROUTING_CONTROL,
OP_READ_SYSTEM_GRAMMATICAL_GENDER,
+ OP_WRITE_SYSTEM_PREFERENCES,
};
@SuppressWarnings("FlaggedApi")
@@ -3144,6 +3167,14 @@ public class AppOpsManager {
Flags.replaceBodySensorPermissionEnabled()
? HealthPermissions.READ_OXYGEN_SATURATION : null)
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_WRITE_SYSTEM_PREFERENCES, OPSTR_WRITE_SYSTEM_PREFERENCES,
+ "WRITE_SYSTEM_PREFERENCES").setPermission(
+ com.android.settingslib.flags.Flags.writeSystemPreferencePermissionEnabled()
+ ? Manifest.permission.WRITE_SYSTEM_PREFERENCES : null).build(),
+ new AppOpInfo.Builder(OP_CONTROL_AUDIO, OPSTR_CONTROL_AUDIO,
+ "CONTROL_AUDIO").setDefaultMode(AppOpsManager.MODE_FOREGROUND).build(),
+ new AppOpInfo.Builder(OP_CONTROL_AUDIO_PARTIAL, OPSTR_CONTROL_AUDIO_PARTIAL,
+ "CONTROL_AUDIO_PARTIAL").setDefaultMode(AppOpsManager.MODE_FOREGROUND).build(),
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index e247916915d6..7e0a9b69b7bd 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -16,6 +16,7 @@
package android.app;
+import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
import static android.app.PropertyInvalidatedCache.createSystemCacheKey;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_NOT_COLORED;
@@ -783,43 +784,24 @@ public class ApplicationPackageManager extends PackageManager {
}
/**
+ * The API and cache name for hasSystemFeature.
+ */
+ private static final String HAS_SYSTEM_FEATURE_API = "has_system_feature";
+
+ /**
* Identifies a single hasSystemFeature query.
*/
- @Immutable
- private static final class HasSystemFeatureQuery {
- public final String name;
- public final int version;
- public HasSystemFeatureQuery(String n, int v) {
- name = n;
- version = v;
- }
- @Override
- public String toString() {
- return String.format("HasSystemFeatureQuery(name=\"%s\", version=%d)",
- name, version);
- }
- @Override
- public boolean equals(@Nullable Object o) {
- if (o instanceof HasSystemFeatureQuery) {
- HasSystemFeatureQuery r = (HasSystemFeatureQuery) o;
- return Objects.equals(name, r.name) && version == r.version;
- } else {
- return false;
- }
- }
- @Override
- public int hashCode() {
- return Objects.hashCode(name) * 13 + version;
- }
- }
+ private record HasSystemFeatureQuery(String name, int version) {}
// Make this cache relatively large. There are many system features and
// none are ever invalidated. MPTS tests suggests that the cache should
// hold at least 150 entries.
private final static PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>
- mHasSystemFeatureCache =
- new PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>(
- 256, createSystemCacheKey("has_system_feature")) {
+ mHasSystemFeatureCache = new PropertyInvalidatedCache<>(
+ new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
+ .api(HAS_SYSTEM_FEATURE_API).maxEntries(256).isolateUids(false),
+ HAS_SYSTEM_FEATURE_API, null) {
+
@Override
public Boolean recompute(HasSystemFeatureQuery query) {
try {
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 961501503348..7a329cd541a2 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -112,8 +112,8 @@ interface IActivityClientController {
oneway void requestMultiwindowFullscreen(in IBinder token, in int request,
in IRemoteCallback callback);
- oneway void startLockTaskModeByToken(in IBinder token);
- oneway void stopLockTaskModeByToken(in IBinder token);
+ void startLockTaskModeByToken(in IBinder token);
+ void stopLockTaskModeByToken(in IBinder token);
oneway void showLockTaskEscapeMessage(in IBinder token);
void setTaskDescription(in IBinder token, in ActivityManager.TaskDescription values);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 34a3ad19b270..a8412fa66609 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -358,7 +358,7 @@ interface IActivityManager {
@UnsupportedAppUsage
void resumeAppSwitches();
boolean bindBackupAgent(in String packageName, int backupRestoreMode, int targetUserId,
- int backupDestination);
+ int backupDestination, boolean useRestrictedMode);
void backupAgentCreated(in String packageName, in IBinder agent, int userId);
void unbindBackupAgent(in ApplicationInfo appInfo);
int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 1dc774285a32..675152fbbbb6 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -1947,10 +1947,12 @@ public class PropertyInvalidatedCache<Query, Result> {
}
// Return true if this cache has had any activity. If the hits, misses, and skips are all
- // zero then the client never tried to use the cache.
- private boolean isActive() {
+ // zero then the client never tried to use the cache. If invalidations and corks are also
+ // zero then the server never tried to use the cache.
+ private boolean isActive(NonceHandler.Stats stats) {
synchronized (mLock) {
- return mHits + mMisses + getSkipsLocked() > 0;
+ return mHits + mMisses + getSkipsLocked()
+ + stats.invalidated + stats.corkedInvalidates > 0;
}
}
@@ -1968,15 +1970,15 @@ public class PropertyInvalidatedCache<Query, Result> {
NonceHandler.Stats stats = mNonce.getStats();
synchronized (mLock) {
- if (brief && !isActive()) {
+ if (brief && !isActive(stats)) {
return;
}
pw.println(formatSimple(" Cache Name: %s", cacheName()));
pw.println(formatSimple(" Property: %s", mPropertyName));
pw.println(formatSimple(
- " Hits: %d, Misses: %d, Skips: %d, Clears: %d, Uids: %d",
- mHits, mMisses, getSkipsLocked(), mClears, mCache.size()));
+ " Hits: %d, Misses: %d, Skips: %d, Clears: %d",
+ mHits, mMisses, getSkipsLocked(), mClears));
// Print all the skip reasons.
pw.format(" Skip-%s: %d", sNonceName[0], mSkips[0]);
@@ -1986,7 +1988,7 @@ public class PropertyInvalidatedCache<Query, Result> {
pw.println();
pw.println(formatSimple(
- " Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d",
+ " Nonce: 0x%016x, Invalidates: %d, Corked: %d",
mLastSeenNonce, stats.invalidated, stats.corkedInvalidates));
pw.println(formatSimple(
" Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index e451116081fa..53a7dad76788 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -17,6 +17,7 @@
package android.app;
import static android.app.appfunctions.flags.Flags.enableAppFunctionManager;
+import static android.provider.flags.Flags.stageFlagsForBuild;
import static android.server.Flags.removeGameManagerServiceFromWear;
import android.accounts.AccountManager;
@@ -163,6 +164,7 @@ import android.media.tv.tunerresourcemanager.ITunerResourceManager;
import android.media.tv.tunerresourcemanager.TunerResourceManager;
import android.nearby.NearbyFrameworkInitializer;
import android.net.ConnectivityFrameworkInitializer;
+import android.net.ConnectivityFrameworkInitializerBaklava;
import android.net.ConnectivityFrameworkInitializerTiramisu;
import android.net.INetworkPolicyManager;
import android.net.IPacProxyManager;
@@ -173,7 +175,6 @@ import android.net.NetworkWatchlistManager;
import android.net.PacProxyManager;
import android.net.TetheringManager;
import android.net.VpnManager;
-import android.net.vcn.VcnFrameworkInitializer;
import android.net.wifi.WifiFrameworkInitializer;
import android.net.wifi.nl80211.WifiNl80211Manager;
import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager;
@@ -190,6 +191,7 @@ import android.os.IBatteryPropertiesRegistrar;
import android.os.IBinder;
import android.os.IDumpstate;
import android.os.IHardwarePropertiesManager;
+import android.os.IHintManager;
import android.os.IPowerManager;
import android.os.IPowerStatsService;
import android.os.IRecoverySystem;
@@ -215,6 +217,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
import android.os.VibratorManager;
+import android.os.flagging.ConfigInfrastructureFrameworkInitializer;
import android.os.health.SystemHealthManager;
import android.os.image.DynamicSystemManager;
import android.os.image.IDynamicSystemService;
@@ -238,6 +241,8 @@ import android.security.advancedprotection.AdvancedProtectionManager;
import android.security.advancedprotection.IAdvancedProtectionService;
import android.security.attestationverification.AttestationVerificationManager;
import android.security.attestationverification.IAttestationVerificationManagerService;
+import android.security.forensic.ForensicManager;
+import android.security.forensic.IForensicService;
import android.security.keystore.KeyStoreManager;
import android.service.oemlock.IOemLockService;
import android.service.oemlock.OemLockManager;
@@ -1195,8 +1200,10 @@ public final class SystemServiceRegistry {
public SystemHealthManager createService(ContextImpl ctx) throws ServiceNotFoundException {
IBinder batteryStats = ServiceManager.getServiceOrThrow(BatteryStats.SERVICE_NAME);
IBinder powerStats = ServiceManager.getService(Context.POWER_STATS_SERVICE);
+ IBinder perfHint = ServiceManager.getService(Context.PERFORMANCE_HINT_SERVICE);
return new SystemHealthManager(IBatteryStats.Stub.asInterface(batteryStats),
- IPowerStatsService.Stub.asInterface(powerStats));
+ IPowerStatsService.Stub.asInterface(powerStats),
+ IHintManager.Stub.asInterface(perfHint));
}});
registerService(Context.CONTEXTHUB_SERVICE, ContextHubManager.class,
@@ -1790,6 +1797,18 @@ public final class SystemServiceRegistry {
}
});
+ registerService(Context.FORENSIC_SERVICE, ForensicManager.class,
+ new CachedServiceFetcher<ForensicManager>() {
+ @Override
+ public ForensicManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(
+ Context.FORENSIC_SERVICE);
+ IForensicService service = IForensicService.Stub.asInterface(b);
+ return new ForensicManager(service);
+ }
+ });
+
sInitializing = true;
try {
// Note: the following functions need to be @SystemApis, once they become mainline
@@ -1818,7 +1837,11 @@ public final class SystemServiceRegistry {
OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers();
DeviceLockFrameworkInitializer.registerServiceWrappers();
VirtualizationFrameworkInitializer.registerServiceWrappers();
- VcnFrameworkInitializer.registerServiceWrappers();
+ ConnectivityFrameworkInitializerBaklava.registerServiceWrappers();
+
+ if (stageFlagsForBuild()) {
+ ConfigInfrastructureFrameworkInitializer.registerServiceWrappers();
+ }
if (com.android.server.telecom.flags.Flags.telecomMainlineBlockedNumbersManager()) {
ProviderFrameworkInitializer.registerServiceWrappers();
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4e68b5af72d2..e766ae2fce0d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16,6 +16,7 @@
package android.app.admin;
+import static android.app.admin.flags.Flags.FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.LOCK_DEVICE;
@@ -17196,11 +17197,14 @@ public class DevicePolicyManager {
* @throws SecurityException if the caller does not hold
* {@link android.Manifest.permission#MANAGE_PROFILE_AND_DEVICE_OWNERS}.
* @throws ProvisioningException if an error occurred during provisioning.
+ * @deprecated Use {@link #createManagedProfile} and {@link #finalizeCreateManagedProfile}
* @hide
*/
@Nullable
@SystemApi
+ @Deprecated
@RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
public UserHandle createAndProvisionManagedProfile(
@NonNull ManagedProfileProvisioningParams provisioningParams)
throws ProvisioningException {
@@ -17218,6 +17222,69 @@ public class DevicePolicyManager {
}
/**
+ * Creates a managed profile and sets the
+ * {@link ManagedProfileProvisioningParams#getProfileAdminComponentName()} as the profile
+ * owner. The method {@link #finalizeCreateManagedProfile} must be called after to finalize the
+ * creation of the managed profile.
+ *
+ * <p>The method {@link #checkProvisioningPrecondition} must return {@link #STATUS_OK}
+ * before calling this method. If it doesn't, a ProvisioningException will be thrown.
+ *
+ * @param provisioningParams Params required to provision a managed profile,
+ * see {@link ManagedProfileProvisioningParams}.
+ * @return The {@link UserHandle} of the created profile or {@code null} if the service is
+ * not available.
+ * @throws ProvisioningException if an error occurred during provisioning.
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
+ public UserHandle createManagedProfile(
+ @NonNull ManagedProfileProvisioningParams provisioningParams)
+ throws ProvisioningException {
+ if (mService == null) {
+ return null;
+ }
+ try {
+ return mService.createManagedProfile(provisioningParams, mContext.getPackageName());
+ } catch (ServiceSpecificException e) {
+ throw new ProvisioningException(e, e.errorCode, getErrorMessage(e));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Finalizes the creation of a managed profile by informing the necessary components that
+ * the managed profile is ready.
+ *
+ * @param provisioningParams Params required to provision a managed profile,
+ * see {@link ManagedProfileProvisioningParams}.
+ * @param managedProfileUser The recently created managed profile.
+ * @throws ProvisioningException if an error occurred during provisioning.
+ * @hide
+ */
+ @SuppressLint("UserHandle")
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
+ public void finalizeCreateManagedProfile(
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull UserHandle managedProfileUser)
+ throws ProvisioningException {
+ if (mService == null) {
+ return;
+ }
+ try {
+ mService.finalizeCreateManagedProfile(provisioningParams, managedProfileUser);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Removes a manged profile from the device only when called from a managed profile's context
*
* @param user UserHandle of the profile to be removed
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index fa984af68016..d048b5371fc4 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -570,6 +570,8 @@ interface IDevicePolicyManager {
void setOrganizationIdForUser(in String callerPackage, in String enterpriseId, int userId);
UserHandle createAndProvisionManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in String callerPackage);
+ UserHandle createManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in String callerPackage);
+ void finalizeCreateManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in UserHandle managedProfileUser);
void provisionFullyManagedDevice(in FullyManagedDeviceProvisioningParams provisioningParams, in String callerPackage);
void finalizeWorkProfileProvisioning(in UserHandle managedProfileUser, in Account migratedAccount);
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 404471e266d2..581efa5d2efa 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -343,6 +343,16 @@ flag {
}
flag {
+ name: "active_admin_cleanup"
+ namespace: "enterprise"
+ description: "Remove ActiveAdmin from EnforcingAdmin and related cleanups"
+ bug: "335663055"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "user_provisioning_same_state"
namespace: "enterprise"
description: "Handle exceptions while setting same provisioning state."
@@ -372,7 +382,7 @@ flag {
is_exported: true
namespace: "enterprise"
description: "Allows DPMS to enable or disable SupervisionService based on whether the device is being managed by the supervision role holder."
- bug: "376213673"
+ bug: "358134581"
}
flag {
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index dcac59c19df4..5004c02194ea 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -16,6 +16,8 @@
package android.app.backup;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Intent;
@@ -27,6 +29,7 @@ import android.os.RemoteException;
import com.android.internal.backup.IBackupTransport;
import com.android.internal.backup.ITransportStatusCallback;
import com.android.internal.infra.AndroidFuture;
+import com.android.server.backup.Flags;
import java.util.Arrays;
import java.util.List;
@@ -671,6 +674,22 @@ public class BackupTransport {
}
/**
+ * Ask the transport whether packages that are about to be backed up or restored should not be
+ * put into a restricted mode by the framework and started normally instead.
+ *
+ * @param operationType 0 for backup, 1 for restore.
+ * @return a subset of the {@code packageNames} passed in, indicating
+ * which packages should NOT be put into restricted mode for the given operation type.
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public List<String> getPackagesThatShouldNotUseRestrictedMode(
+ @NonNull List<String> packageNames,
+ @BackupAnnotations.OperationType int operationType) {
+ return List.of();
+ }
+
+ /**
* Bridge between the actual IBackupTransport implementation and the stable API. If the
* binder interface needs to change, we use this layer to translate so that we can
* (if appropriate) decouple those framework-side changes from the BackupTransport
@@ -977,5 +996,19 @@ public class BackupTransport {
resultFuture.cancel(/* mayInterruptIfRunning */ true);
}
}
+
+ @Override
+ @FlaggedApi(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void getPackagesThatShouldNotUseRestrictedMode(List<String> packageNames,
+ int operationType, AndroidFuture<List<String>> resultFuture) {
+ try {
+ List<String> result =
+ BackupTransport.this.getPackagesThatShouldNotUseRestrictedMode(packageNames,
+ operationType);
+ resultFuture.complete(result);
+ } catch (RuntimeException e) {
+ resultFuture.cancel(/* mayInterruptIfRunning */ true);
+ }
+ }
}
}
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java b/core/java/android/app/supervision/SupervisionManagerInternal.java
index 5df9dd521092..d571e14ff5fa 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java
+++ b/core/java/android/app/supervision/SupervisionManagerInternal.java
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.server.supervision;
+package android.app.supervision;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.os.Bundle;
+import android.os.PersistableBundle;
/**
* Local system service interface for {@link SupervisionService}.
@@ -35,6 +35,11 @@ public abstract class SupervisionManagerInternal {
public abstract boolean isSupervisionEnabledForUser(@UserIdInt int userId);
/**
+ * Returns whether the supervision lock screen needs to be shown.
+ */
+ public abstract boolean isSupervisionLockscreenEnabledForUser(@UserIdInt int userId);
+
+ /**
* Set whether supervision is enabled for the specified user.
*
* @param userId The user to set the supervision state for
@@ -50,5 +55,5 @@ public abstract class SupervisionManagerInternal {
* @param options Optional configuration parameters for the supervision lock screen
*/
public abstract void setSupervisionLockscreenEnabledForUser(
- @UserIdInt int userId, boolean enabled, @Nullable Bundle options);
+ @UserIdInt int userId, boolean enabled, @Nullable PersistableBundle options);
}
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index c47fe236faf0..de01280f293f 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -148,6 +148,14 @@ flag {
flag {
namespace: "virtual_devices"
+ name: "notifications_for_device_streaming"
+ description: "Add notifications permissions to device streaming role"
+ bug: "375240276"
+ is_exported: true
+}
+
+flag {
+ namespace: "virtual_devices"
name: "default_device_camera_access_policy"
description: "API for default device camera access policy"
bug: "371173368"
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 3152ff4564fe..37295ac94823 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -193,6 +193,42 @@ public abstract class PackageManager {
"android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES";
/**
+ * &lt;application&gt; level {@link android.content.pm.PackageManager.Property} tag
+ * specifying whether the app should be put into the "restricted" backup mode when it's started
+ * for backup and restore operations.
+ *
+ * <p> See <a
+ * href="https://developer.android.com/identity/data/autobackup#ImplementingBackupAgent"> for
+ * information about restricted mode</a>.
+ *
+ * <p> Starting with Android 16 apps may not be started in restricted mode based on this
+ * property.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * &lt;application&gt;
+ * &lt;property
+ * android:name="android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE"
+ * android:value="true|false"/&gt;
+ * &lt;/application&gt;
+ * </pre>
+ *
+ * <p>If this property is set, the operating system will respect it for now (see Note below).
+ * If it's not set, the behavior depends on the SDK level that the app is targeting. For apps
+ * targeting SDK level {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or lower, the
+ * property defaults to {@code true}. For apps targeting SDK level
+ * {@link android.os.Build.VERSION_CODES#BAKLAVA} or higher, the operating system will make a
+ * decision dynamically.
+ *
+ * <p>Note: It's not recommended to set this property to {@code true} unless absolutely
+ * necessary. In a future Android version, this property may be deprecated in favor of removing
+ * restricted mode completely.
+ */
+ @FlaggedApi(com.android.server.backup.Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public static final String PROPERTY_USE_RESTRICTED_BACKUP_MODE =
+ "android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE";
+
+ /**
* Application level property that an app can specify to opt-out from having private data
* directories both on the internal and external storages.
*
@@ -292,6 +328,10 @@ public abstract class PackageManager {
* <p>
* The value of a property will only have a single type, as defined by
* the property itself.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * In android version {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and earlier,
+ * the {@code equals} and {@code hashCode} methods for this class may not function as expected.
*/
public static final class Property implements Parcelable {
private static final int TYPE_BOOLEAN = 1;
@@ -523,6 +563,40 @@ public abstract class PackageManager {
return new Property[size];
}
};
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Property)) {
+ return false;
+ }
+ final Property property = (Property) obj;
+ return mType == property.mType &&
+ Objects.equals(mName, property.mName) &&
+ Objects.equals(mClassName, property.mClassName) &&
+ Objects.equals(mPackageName, property.mPackageName) &&
+ (mType == TYPE_BOOLEAN ? mBooleanValue == property.mBooleanValue :
+ mType == TYPE_FLOAT ? Float.compare(mFloatValue, property.mFloatValue) == 0 :
+ mType == TYPE_INTEGER ? mIntegerValue == property.mIntegerValue :
+ mType == TYPE_RESOURCE ? mIntegerValue == property.mIntegerValue :
+ mStringValue.equals(property.mStringValue));
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(mName, mType, mClassName, mPackageName);
+ if (mType == TYPE_BOOLEAN) {
+ result = 31 * result + (mBooleanValue ? 1 : 0);
+ } else if (mType == TYPE_FLOAT) {
+ result = 31 * result + Float.floatToIntBits(mFloatValue);
+ } else if (mType == TYPE_INTEGER) {
+ result = 31 * result + mIntegerValue;
+ } else if (mType == TYPE_RESOURCE) {
+ result = 31 * result + mIntegerValue;
+ } else if (mType == TYPE_STRING) {
+ result = 31 * result + mStringValue.hashCode();
+ }
+ return result;
+ }
}
/**
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index f7191e605fb8..5dfec9809f6e 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -105,6 +105,8 @@ public final class SharedLibraryInfo implements Parcelable {
private final List<VersionedPackage> mOptionalDependentPackages;
private List<SharedLibraryInfo> mDependencies;
+ private final List<String> mCertDigests;
+
/**
* Creates a new instance.
*
@@ -134,6 +136,7 @@ public final class SharedLibraryInfo implements Parcelable {
mDependencies = dependencies;
mIsNative = isNative;
mOptionalDependentPackages = null;
+ mCertDigests = null;
}
/**
@@ -165,6 +168,7 @@ public final class SharedLibraryInfo implements Parcelable {
mDeclaringPackage = declaringPackage;
mDependencies = dependencies;
mIsNative = isNative;
+ mCertDigests = null;
var allDependents = allDependentPackages.first;
var usesLibOptional = allDependentPackages.second;
@@ -206,6 +210,7 @@ public final class SharedLibraryInfo implements Parcelable {
mIsNative = parcel.readBoolean();
mOptionalDependentPackages = parcel.readParcelableList(new ArrayList<>(),
VersionedPackage.class.getClassLoader(), VersionedPackage.class);
+ mCertDigests = parcel.createStringArrayList();
}
/**
@@ -214,6 +219,29 @@ public final class SharedLibraryInfo implements Parcelable {
* @param versionMajor
*/
public SharedLibraryInfo(String name, long versionMajor, int type) {
+ //TODO: change to this(name, versionMajor, type, /* certDigest= */null); after flag removal
+ mPath = null;
+ mPackageName = null;
+ mName = name;
+ mVersion = versionMajor;
+ mType = type;
+ mDeclaringPackage = null;
+ mDependentPackages = null;
+ mDependencies = null;
+ mIsNative = false;
+ mOptionalDependentPackages = null;
+ mCertDigests = null;
+ }
+
+ /**
+ * @hide
+ * @param name The lib name.
+ * @param versionMajor The lib major version.
+ * @param type The type of shared library.
+ * @param certDigests The list of certificate digests for this shared library.
+ */
+ @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ public SharedLibraryInfo(String name, long versionMajor, int type, List<String> certDigests) {
mPath = null;
mPackageName = null;
mName = name;
@@ -224,6 +252,7 @@ public final class SharedLibraryInfo implements Parcelable {
mDependencies = null;
mIsNative = false;
mOptionalDependentPackages = null;
+ mCertDigests = certDigests;
}
/**
@@ -433,6 +462,19 @@ public final class SharedLibraryInfo implements Parcelable {
return mOptionalDependentPackages;
}
+ /**
+ * Gets the list of certificate digests for the shared library.
+ *
+ * @return The list of certificate digests
+ */
+ @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ public @NonNull List<String> getCertDigests() {
+ if (mCertDigests == null) {
+ return Collections.emptyList();
+ }
+ return mCertDigests;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -463,6 +505,7 @@ public final class SharedLibraryInfo implements Parcelable {
parcel.writeTypedList(mDependencies);
parcel.writeBoolean(mIsNative);
parcel.writeParcelableList(mOptionalDependentPackages, flags);
+ parcel.writeStringList(mCertDigests);
}
private static String typeToString(int type) {
diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl
new file mode 100644
index 000000000000..06fcabcf55e9
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dependencyinstaller;
+
+parcelable DependencyInstallerCallback; \ No newline at end of file
diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
new file mode 100644
index 000000000000..ba089f7fd33e
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.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.content.pm.dependencyinstaller;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.pm.Flags;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+/**
+ * Callbacks for {@link DependencyInstallerService}. The implementation of
+ * DependencyInstallerService uses this interface to indicate completion of the session creation
+ * request given by the system server.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+public final class DependencyInstallerCallback implements Parcelable {
+ private final IBinder mBinder;
+ private final IDependencyInstallerCallback mCallback;
+
+ /** @hide */
+ public DependencyInstallerCallback(IBinder binder) {
+ mBinder = binder;
+ mCallback = IDependencyInstallerCallback.Stub.asInterface(binder);
+ }
+
+ private DependencyInstallerCallback(Parcel in) {
+ mBinder = in.readStrongBinder();
+ mCallback = IDependencyInstallerCallback.Stub.asInterface(mBinder);
+ }
+
+ /**
+ * Callback to indicate that all the requested dependencies have been resolved and their
+ * sessions created. See {@link DependencyInstallerService#onDependenciesRequired}.
+ *
+ * @param sessionIds the install session IDs for all requested dependencies
+ */
+ public void onAllDependenciesResolved(@NonNull int[] sessionIds) {
+ try {
+ mCallback.onAllDependenciesResolved(sessionIds);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Callback to indicate that at least one of the required dependencies could not be resolved
+ * and any associated sessions have been abandoned.
+ */
+ public void onFailureToResolveAllDependencies() {
+ try {
+ mCallback.onFailureToResolveAllDependencies();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeStrongBinder(mBinder);
+ }
+
+ public static final @NonNull Creator<DependencyInstallerCallback> CREATOR =
+ new Creator<>() {
+ @Override
+ public DependencyInstallerCallback createFromParcel(Parcel in) {
+ return new DependencyInstallerCallback(in);
+ }
+
+ @Override
+ public DependencyInstallerCallback[] newArray(int size) {
+ return new DependencyInstallerCallback[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java
new file mode 100644
index 000000000000..11864150e072
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java
@@ -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 android.content.pm.dependencyinstaller;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.Flags;
+import android.content.pm.SharedLibraryInfo;
+import android.os.IBinder;
+
+import java.util.List;
+
+/**
+ * Service that needs to be implemented by the holder of the DependencyInstaller role. This service
+ * will be invoked by the system during application installations if it depends on
+ * {@link android.content.pm.SharedLibraryInfo#TYPE_STATIC} or
+ * {@link android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE} and those dependencies aren't
+ * already installed.
+ * <p>
+ * Below is an example manifest registration for a {@code DependencyInstallerService}.
+ * <pre>
+ * {@code
+ * <service android:name=".ExampleDependencyInstallerService"
+ * android:permission="android.permission.BIND_DEPENDENCY_INSTALLER" >
+ * ...
+ * <intent-filter>
+ * <action android:name="android.content.pm.action.INSTALL_DEPENDENCY" />
+ * </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+public abstract class DependencyInstallerService extends Service {
+
+ private IDependencyInstallerService mBinder;
+
+ @Override
+ public final @NonNull IBinder onBind(@Nullable Intent intent) {
+ if (mBinder == null) {
+ mBinder = new IDependencyInstallerService.Stub() {
+ @Override
+ public void onDependenciesRequired(List<SharedLibraryInfo> neededLibraries,
+ DependencyInstallerCallback callback) {
+ DependencyInstallerService.this.onDependenciesRequired(neededLibraries,
+ callback);
+ }
+ };
+ }
+ return mBinder.asBinder();
+ }
+
+ /**
+ * Notify the holder of the DependencyInstaller role of the missing dependencies required for
+ * the completion of an active install session.
+ *
+ * @param neededLibraries the list of shared library dependencies needed to be obtained and
+ * installed.
+ */
+ public abstract void onDependenciesRequired(@NonNull List<SharedLibraryInfo> neededLibraries,
+ @NonNull DependencyInstallerCallback callback);
+}
diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
new file mode 100644
index 000000000000..92d1d9e118e6
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
@@ -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 android.content.pm.dependencyinstaller;
+
+import java.util.List;
+
+/**
+* Callbacks for Dependency Installer. The app side invokes on this interface to indicate
+* completion of the async dependency install request given by the system server.
+*
+* {@hide}
+*/
+oneway interface IDependencyInstallerCallback {
+ /**
+ * Callback to indicate that all the requested dependencies have been resolved and have been
+ * committed for installation. See {@link DependencyInstallerService#onDependenciesRequired}.
+ *
+ * @param sessionIds the install session IDs for all requested dependencies
+ */
+ void onAllDependenciesResolved(in int[] sessionIds);
+
+ /**
+ * Callback to indicate that at least one of the required dependencies could not be resolved
+ * and any associated sessions have been abandoned.
+ */
+ void onFailureToResolveAllDependencies();
+} \ No newline at end of file
diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl
new file mode 100644
index 000000000000..94f5bf45ca9c
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl
@@ -0,0 +1,37 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dependencyinstaller;
+
+import android.content.pm.dependencyinstaller.DependencyInstallerCallback;
+import android.content.pm.SharedLibraryInfo;
+import java.util.List;
+
+/**
+* Interface used to communicate with the application code that holds the Dependency Installer role.
+* {@hide}
+*/
+oneway interface IDependencyInstallerService {
+ /**
+ * Notify dependency installer of the required dependencies to complete the current install
+ * session.
+ *
+ * @param neededLibraries the list of shared library dependencies needed to be obtained and
+ * installed.
+ */
+ void onDependenciesRequired(in List<SharedLibraryInfo> neededLibraries,
+ in DependencyInstallerCallback callback);
+ } \ No newline at end of file
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 9ba5a352358b..e181ae8ef3c7 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -366,3 +366,13 @@ flag {
description: "Block app installations that specify an incompatible minor SDK version"
bug: "377474232"
}
+
+flag {
+ name: "app_compat_option_16kb"
+ is_exported: true
+ namespace: "devoptions_settings"
+ description: "Feature flag to enable page size app compat mode from manifest, package manager and settings level."
+ bug: "371049373"
+ is_fixed_read_only: true
+}
+
diff --git a/core/java/android/hardware/DisplayLuts.java b/core/java/android/hardware/DisplayLuts.java
index b162ad6e2d15..6343ba19f569 100644
--- a/core/java/android/hardware/DisplayLuts.java
+++ b/core/java/android/hardware/DisplayLuts.java
@@ -16,116 +16,294 @@
package android.hardware;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.hardware.flags.Flags;
import android.util.IntArray;
import java.util.ArrayList;
-import java.util.List;
/**
- * @hide
+ * DisplayLuts provides the developers to apply Lookup Tables (Luts) to a
+ * {@link android.view.SurfaceControl}. Luts provides ways to control tonemapping
+ * for specific content.
+ *
+ * The general flow is as follows:
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/DisplayLuts.png" />
+ * <figcaption style="text-align: center;">DisplayLuts flow</figcaption>
+ * </p>
+ *
+ * @see LutProperties
*/
+@FlaggedApi(Flags.FLAG_LUTS_API)
public final class DisplayLuts {
+ private ArrayList<Entry> mEntries;
private IntArray mOffsets;
private int mTotalLength;
- private List<float[]> mLutBuffers;
- private IntArray mLutDimensions;
- private IntArray mLutSizes;
- private IntArray mLutSamplingKeys;
- private static final int LUT_LENGTH_LIMIT = 100000;
-
+ /**
+ * Create a {@link DisplayLuts} instance.
+ */
+ @FlaggedApi(Flags.FLAG_LUTS_API)
public DisplayLuts() {
+ mEntries = new ArrayList<>();
mOffsets = new IntArray();
mTotalLength = 0;
-
- mLutBuffers = new ArrayList<>();
- mLutDimensions = new IntArray();
- mLutSizes = new IntArray();
- mLutSamplingKeys = new IntArray();
}
- /**
- * Add the lut to be applied.
- *
- * @param buffer
- * @param dimension either 1D or 3D
- * @param size
- * @param samplingKey
- */
- public void addLut(@NonNull float[] buffer, @LutProperties.Dimension int dimension,
- int size, @LutProperties.SamplingKey int samplingKey) {
+ @FlaggedApi(Flags.FLAG_LUTS_API)
+ public static class Entry {
+ private float[] mBuffer;
+ private @LutProperties.Dimension int mDimension;
+ private int mSize;
+ private @LutProperties.SamplingKey int mSamplingKey;
+
+ private static final int LUT_LENGTH_LIMIT = 100000;
+
+ /**
+ * Create a Lut entry.
+ *
+ * <p>
+ * Noted that 1D Lut(s) are treated as gain curves.
+ * For 3D Lut(s), 3D Lut(s) are used for direct color manipulations.
+ * The values of 3D Lut(s) data should be normalized to the range {@code 0.0}
+ * to {@code 1.0}, inclusive. And 3D Lut(s) data is organized in the order of
+ * R, G, B channels.
+ *
+ * @param buffer The raw lut data
+ * @param dimension Either 1D or 3D
+ * @param samplingKey The sampling kay used for the Lut
+ */
+ @FlaggedApi(Flags.FLAG_LUTS_API)
+ public Entry(@NonNull float[] buffer,
+ @LutProperties.Dimension int dimension,
+ @LutProperties.SamplingKey int samplingKey) {
+ if (buffer == null || buffer.length < 1) {
+ throw new IllegalArgumentException("The buffer cannot be empty!");
+ }
+
+ if (buffer.length >= LUT_LENGTH_LIMIT) {
+ throw new IllegalArgumentException("The lut length is too big to handle!");
+ }
+
+ if (dimension != LutProperties.ONE_DIMENSION
+ && dimension != LutProperties.THREE_DIMENSION) {
+ throw new IllegalArgumentException("The dimension should be either 1D or 3D!");
+ }
+
+ if (dimension == LutProperties.THREE_DIMENSION) {
+ if (buffer.length <= 3) {
+ throw new IllegalArgumentException(
+ "The 3d lut size of each dimension should be over 1!");
+ }
+ int lengthPerChannel = buffer.length;
+ if (lengthPerChannel % 3 != 0) {
+ throw new IllegalArgumentException(
+ "The lut buffer of 3dlut should have 3 channels!");
+ }
+ lengthPerChannel /= 3;
- int lutLength = 0;
- if (dimension == LutProperties.ONE_DIMENSION) {
- lutLength = size;
- } else if (dimension == LutProperties.THREE_DIMENSION) {
- lutLength = size * size * size;
- } else {
- clear();
- throw new IllegalArgumentException("The dimension is either 1D or 3D!");
+ double size = Math.cbrt(lengthPerChannel);
+ if (size == (int) size) {
+ mSize = (int) size;
+ } else {
+ throw new IllegalArgumentException(
+ "Cannot get the cube root of the 3d lut buffer!");
+ }
+ } else {
+ mSize = buffer.length;
+ }
+
+ mBuffer = buffer;
+ mDimension = dimension;
+ mSamplingKey = samplingKey;
}
- if (lutLength >= LUT_LENGTH_LIMIT) {
- clear();
- throw new IllegalArgumentException("The lut length is too big to handle!");
+ /**
+ * @return the dimension of the lut entry
+ */
+ @FlaggedApi(Flags.FLAG_LUTS_API)
+ public int getDimension() {
+ return mDimension;
}
- mOffsets.add(mTotalLength);
- mTotalLength += lutLength;
+ /**
+ * @return the size of the lut for each dimension
+ * @hide
+ */
+ public int getSize() {
+ return mSize;
+ }
+
+ /**
+ * @return the lut raw data of the lut
+ */
+ @FlaggedApi(Flags.FLAG_LUTS_API)
+ public @NonNull float[] getBuffer() {
+ return mBuffer;
+ }
+
+ /**
+ * @return the sampling key used by the lut
+ */
+ @FlaggedApi(Flags.FLAG_LUTS_API)
+ public int getSamplingKey() {
+ return mSamplingKey;
+ }
+
+ @Override
+ public String toString() {
+ return "Entry{"
+ + "dimension=" + DisplayLuts.Entry.dimensionToString(getDimension())
+ + ", size(each dimension)=" + getSize()
+ + ", samplingKey=" + samplingKeyToString(getSamplingKey()) + "}";
+ }
+
+ private static String dimensionToString(int dimension) {
+ switch(dimension) {
+ case LutProperties.ONE_DIMENSION:
+ return "ONE_DIMENSION";
+ case LutProperties.THREE_DIMENSION:
+ return "THREE_DIMENSION";
+ default:
+ return "";
+ }
+ }
+
+ private static String samplingKeyToString(int key) {
+ switch(key) {
+ case LutProperties.SAMPLING_KEY_RGB:
+ return "SAMPLING_KEY_RGB";
+ case LutProperties.SAMPLING_KEY_MAX_RGB:
+ return "SAMPLING_KEY_MAX_RGB";
+ default:
+ return "";
+ }
+ }
+ }
- mLutBuffers.add(buffer);
- mLutDimensions.add(dimension);
- mLutSizes.add(size);
- mLutSamplingKeys.add(samplingKey);
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("DisplayLuts{");
+ sb.append("\n");
+ for (DisplayLuts.Entry entry: mEntries) {
+ sb.append(entry.toString());
+ sb.append("\n");
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ private void addEntry(Entry entry) {
+ mEntries.add(entry);
+ mOffsets.add(mTotalLength);
+ mTotalLength += entry.getBuffer().length;
}
private void clear() {
- mTotalLength = 0;
mOffsets.clear();
- mLutBuffers.clear();
- mLutDimensions.clear();
- mLutSamplingKeys.clear();
+ mTotalLength = 0;
+ mEntries.clear();
+ }
+
+ /**
+ * Set a Lut to be applied.
+ *
+ * <p>Use either this or {@link #set(Entry, Entry)}. The function will
+ * replace any previously set lut(s).</p>
+ *
+ * @param entry Either an 1D Lut or a 3D Lut
+ */
+ @FlaggedApi(Flags.FLAG_LUTS_API)
+ public void set(@NonNull Entry entry) {
+ if (entry == null) {
+ throw new IllegalArgumentException("The entry is null!");
+ }
+ clear();
+ addEntry(entry);
+ }
+
+ /**
+ * Set Luts in order to be applied.
+ *
+ * <p> An 1D Lut and 3D Lut will be applied in order. Use either this or
+ * {@link #set(Entry)}. The function will replace any previously set lut(s)</p>
+ *
+ * @param first An 1D Lut
+ * @param second A 3D Lut
+ */
+ @FlaggedApi(Flags.FLAG_LUTS_API)
+ public void set(@NonNull Entry first, @NonNull Entry second) {
+ if (first == null || second == null) {
+ throw new IllegalArgumentException("The entry is null!");
+ }
+ if (first.getDimension() != LutProperties.ONE_DIMENSION
+ || second.getDimension() != LutProperties.THREE_DIMENSION) {
+ throw new IllegalArgumentException("The entries should be 1D and 3D in order!");
+ }
+ clear();
+ addEntry(first);
+ addEntry(second);
}
/**
- * @return the array of Lut buffers
+ * @hide
+ */
+ public boolean valid() {
+ return mEntries.size() > 0;
+ }
+
+ /**
+ * @hide
*/
public float[] getLutBuffers() {
float[] buffer = new float[mTotalLength];
- for (int i = 0; i < mLutBuffers.size(); i++) {
- float[] lutBuffer = mLutBuffers.get(i);
+ for (int i = 0; i < mEntries.size(); i++) {
+ float[] lutBuffer = mEntries.get(i).getBuffer();
System.arraycopy(lutBuffer, 0, buffer, mOffsets.get(i), lutBuffer.length);
}
return buffer;
}
/**
- * @return the starting point of each lut memory region of the lut buffer
+ * @hide
*/
public int[] getOffsets() {
return mOffsets.toArray();
}
/**
- * @return the array of Lut size
+ * @hide
*/
public int[] getLutSizes() {
- return mLutSizes.toArray();
+ int[] sizes = new int[mEntries.size()];
+ for (int i = 0; i < mEntries.size(); i++) {
+ sizes[i] = mEntries.get(i).getSize();
+ }
+ return sizes;
}
/**
- * @return the array of Lut dimension
+ * @hide
*/
public int[] getLutDimensions() {
- return mLutDimensions.toArray();
+ int[] dimensions = new int[mEntries.size()];
+ for (int i = 0; i < mEntries.size(); i++) {
+ dimensions[i] = mEntries.get(i).getDimension();
+ }
+ return dimensions;
}
/**
- * @return the array of sampling key
+ * @hide
*/
public int[] getLutSamplingKeys() {
- return mLutSamplingKeys.toArray();
+ int[] samplingKeys = new int[mEntries.size()];
+ for (int i = 0; i < mEntries.size(); i++) {
+ samplingKeys[i] = mEntries.get(i).getSamplingKey();
+ }
+ return samplingKeys;
}
}
diff --git a/core/java/android/hardware/LutProperties.java b/core/java/android/hardware/LutProperties.java
index c9c6d6d08ed2..bf40a415b0f7 100644
--- a/core/java/android/hardware/LutProperties.java
+++ b/core/java/android/hardware/LutProperties.java
@@ -16,23 +16,31 @@
package android.hardware;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
- * Lut properties class.
+ * Provides Lut properties of the device.
*
- * A Lut (Look-Up Table) is a pre-calculated table for color transformation.
- *
- * @hide
+ * <p>
+ * A Lut (Look-Up Table) is a pre-calculated table for color correction.
+ * Applications may be interested in the Lut properties exposed by
+ * this class to determine if the Lut(s) they select using
+ * {@link android.view.SurfaceControl.Transaction#setLuts} are by the HWC.
+ * </p>
*/
+@FlaggedApi(Flags.FLAG_LUTS_API)
public final class LutProperties {
private final @Dimension int mDimension;
private final int mSize;
private final @SamplingKey int[] mSamplingKeys;
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"SAMPLING_KEY_"}, value = {
SAMPLING_KEY_RGB,
@@ -42,11 +50,14 @@ public final class LutProperties {
}
/** use r,g,b channel as the gain value of a Lut */
+ @FlaggedApi(Flags.FLAG_LUTS_API)
public static final int SAMPLING_KEY_RGB = 0;
/** use max of r,g,b channel as the gain value of a Lut */
+ @FlaggedApi(Flags.FLAG_LUTS_API)
public static final int SAMPLING_KEY_MAX_RGB = 1;
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
ONE_DIMENSION,
@@ -56,18 +67,22 @@ public final class LutProperties {
}
/** The Lut is one dimensional */
+ @FlaggedApi(Flags.FLAG_LUTS_API)
public static final int ONE_DIMENSION = 1;
/** The Lut is three dimensional */
+ @FlaggedApi(Flags.FLAG_LUTS_API)
public static final int THREE_DIMENSION = 3;
+ @FlaggedApi(Flags.FLAG_LUTS_API)
public @Dimension int getDimension() {
return mDimension;
}
/**
- * @return the size of the Lut.
+ * @return the size of the Lut for each dimension
*/
+ @FlaggedApi(Flags.FLAG_LUTS_API)
public int getSize() {
return mSize;
}
@@ -75,6 +90,8 @@ public final class LutProperties {
/**
* @return the list of sampling keys
*/
+ @FlaggedApi(Flags.FLAG_LUTS_API)
+ @NonNull
public @SamplingKey int[] getSamplingKeys() {
if (mSamplingKeys.length == 0) {
throw new IllegalStateException("no sampling key!");
diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 24cfc1b53e00..d42bfae23d2b 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -18,6 +18,7 @@ package android.hardware;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.hardware.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -72,9 +73,11 @@ public final class OverlayProperties implements Parcelable {
}
/**
- * Gets the lut properties of the display.
- * @hide
+ * Returns the lut properties of the device.
*/
+ @FlaggedApi(Flags.FLAG_LUTS_API)
+ @SuppressLint("ArrayReturn")
+ @NonNull
public LutProperties[] getLutProperties() {
if (mNativeObject == 0) {
return null;
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 58e524e741b3..5533a640b9d8 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -19,9 +19,9 @@ package android.hardware.camera2;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.impl.ExtensionKey;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
@@ -6172,6 +6172,66 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION =
new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.jpegr.availableJpegRStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+ /**
+ * <p>Color space used for shared session configuration for all the output targets
+ * when camera is opened in shared mode. This should be one of the values specified in
+ * availableColorSpaceProfilesMap.</p>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #SHARED_SESSION_COLOR_SPACE_UNSPECIFIED UNSPECIFIED}</li>
+ * <li>{@link #SHARED_SESSION_COLOR_SPACE_SRGB SRGB}</li>
+ * <li>{@link #SHARED_SESSION_COLOR_SPACE_DISPLAY_P3 DISPLAY_P3}</li>
+ * <li>{@link #SHARED_SESSION_COLOR_SPACE_BT2020_HLG BT2020_HLG}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * @see #SHARED_SESSION_COLOR_SPACE_UNSPECIFIED
+ * @see #SHARED_SESSION_COLOR_SPACE_SRGB
+ * @see #SHARED_SESSION_COLOR_SPACE_DISPLAY_P3
+ * @see #SHARED_SESSION_COLOR_SPACE_BT2020_HLG
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ public static final Key<Integer> SHARED_SESSION_COLOR_SPACE =
+ new Key<Integer>("android.sharedSession.colorSpace", int.class);
+
+ /**
+ * <p>List of shared output configurations that this camera device supports when
+ * camera is opened in shared mode. Array contains following entries for each supported
+ * shared configuration:
+ * 1) surface type
+ * 2) width
+ * 3) height
+ * 4) format
+ * 5) mirrorMode
+ * 6) useReadoutTimestamp
+ * 7) timestampBase
+ * 8) dataspace
+ * 9) usage
+ * 10) streamUsecase
+ * 11) physical camera id len
+ * 12) physical camera id as UTF-8 null terminated string.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ public static final Key<long[]> SHARED_SESSION_OUTPUT_CONFIGURATIONS =
+ new Key<long[]>("android.sharedSession.outputConfigurations", long[].class);
+
+ /**
+ * <p>The available stream configurations that this camera device supports for
+ * shared capture session when camera is opened in shared mode. Android camera framework
+ * will generate this tag if the camera device can be opened in shared mode.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ @SyntheticKey
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ public static final Key<android.hardware.camera2.params.SharedSessionConfiguration> SHARED_SESSION_CONFIGURATION =
+ new Key<android.hardware.camera2.params.SharedSessionConfiguration>("android.sharedSession.configuration", android.hardware.camera2.params.SharedSessionConfiguration.class);
+
/**
* Mapping from INFO_SESSION_CONFIGURATION_QUERY_VERSION to session characteristics key.
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index fb381d97adc3..852f04793f15 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -446,6 +446,17 @@ public abstract class CameraDevice implements AutoCloseable {
public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED =
1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
+ /**
+ * Shared camera operation mode.
+ *
+ * @see #CameraSharedCaptureSession
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ public static final int SESSION_OPERATION_MODE_SHARED =
+ 2; // ICameraDeviceUser.SHARED_MODE;
+
/**
* First vendor-specific operating mode
*
@@ -461,6 +472,7 @@ public abstract class CameraDevice implements AutoCloseable {
@IntDef(prefix = {"SESSION_OPERATION_MODE"}, value =
{SESSION_OPERATION_MODE_NORMAL,
SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED,
+ SESSION_OPERATION_MODE_SHARED,
SESSION_OPERATION_MODE_VENDOR_START})
public @interface SessionOperatingMode {};
@@ -1240,7 +1252,6 @@ public abstract class CameraDevice implements AutoCloseable {
*
* </ul>
*
- *
* @param config A session configuration (see {@link SessionConfiguration}).
*
* @throws IllegalArgumentException In case the session configuration is invalid; or the output
@@ -1559,6 +1570,48 @@ public abstract class CameraDevice implements AutoCloseable {
public abstract void onOpened(@NonNull CameraDevice camera); // Must implement
/**
+ * The method called when a camera device has finished opening in shared mode,
+ * where there can be more than one client accessing the same camera.
+ *
+ * <p>At this point, the camera device is ready to use, and
+ * {@link CameraDevice#createCaptureSession} can be called to set up the shared capture
+ * session.</p>
+ *
+ * @param camera the camera device that has become opened
+ * @param isPrimaryClient true if the client opening the camera is currently the primary
+ * client.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ public void onOpenedInSharedMode(@NonNull CameraDevice camera, boolean isPrimaryClient) {
+ // Default empty implementation
+ }
+
+ /**
+ * The method called when client access priorities have changed for a camera device opened
+ * in shared mode where there can be more than one client accessing the same camera.
+ *
+ * If the client priority changed from secondary to primary, then it can now
+ * create capture request and change the capture request parameters. If client priority
+ * changed from primary to secondary, that implies that a higher priority client has also
+ * opened the camera in shared mode and the new client is now a primary client
+ *
+ * @param camera the camera device whose access priorities have changed.
+ * @param isPrimaryClient true if the client is now the primary client.
+ * false if another higher priority client also opened the
+ * camera and is now the new primary client and this client is
+ * now a secondary client.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ public void onClientSharedAccessPriorityChanged(@NonNull CameraDevice camera,
+ boolean isPrimaryClient) {
+ // Default empty implementation
+ }
+
+ /**
* The method called when a camera device has been closed with
* {@link CameraDevice#close}.
*
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 75e20582b7b4..266efb7b759c 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -976,6 +976,46 @@ public final class CameraManager {
}
/**
+ * Checks if a camera device can be opened in a shared mode for a given {@code cameraId}.
+ * If this method returns false for a {@code cameraId}, calling {@link #openSharedCamera}
+ * for that {@code cameraId} will throw an {@link UnsupportedOperationException}.
+ *
+ * @param cameraId The unique identifier of the camera device for which sharing support is
+ * being queried. This identifier must be present in
+ * {@link #getCameraIdList()}.
+ *
+ * @return {@code true} if camera can be opened in shared mode
+ * for the provided {@code cameraId}; {@code false} otherwise.
+ *
+ * @throws IllegalArgumentException If {@code cameraId} is null, or if {@code cameraId} does not
+ * match any device in {@link #getCameraIdList()}.
+ * @throws CameraAccessException if the camera device has been disconnected.
+ *
+ * @see #getCameraIdList()
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ @SystemApi
+ public boolean isCameraDeviceSharingSupported(@NonNull String cameraId)
+ throws CameraAccessException {
+ if (cameraId == null) {
+ throw new IllegalArgumentException("Camera ID was null");
+ }
+
+ if (CameraManagerGlobal.sCameraServiceDisabled
+ || !Arrays.asList(CameraManagerGlobal.get().getCameraIdList(mContext.getDeviceId(),
+ getDevicePolicyFromContext(mContext))).contains(cameraId)) {
+ throw new IllegalArgumentException(
+ "Camera ID '" + cameraId + "' not available on device.");
+ }
+
+ CameraCharacteristics chars = getCameraCharacteristics(cameraId);
+ long[] sharedOutputConfiguration =
+ chars.get(CameraCharacteristics.SHARED_SESSION_OUTPUT_CONFIGURATIONS);
+ return (sharedOutputConfiguration != null);
+ }
+
+ /**
* Retrieves the AttributionSourceState to pass to the CameraService.
*
* @param deviceIdOverride An override of the AttributionSource's deviceId, if not equal to
@@ -1036,6 +1076,9 @@ public final class CameraManager {
* @param cameraId The unique identifier of the camera device to open
* @param callback The callback for the camera. Must not be null.
* @param executor The executor to invoke the callback with. Must not be null.
+ * @param oomScoreOffset The minimum oom score that cameraservice must see for this client.
+ * @param rotationOverride The type of rotation override.
+ * @param sharedMode Parameter specifying if the camera should be opened in shared mode.
*
* @throws CameraAccessException if the camera is disabled by device policy,
* too many camera devices are already open, or the cameraId does not match
@@ -1051,7 +1094,8 @@ public final class CameraManager {
*/
private CameraDevice openCameraDeviceUserAsync(String cameraId,
CameraDevice.StateCallback callback, Executor executor,
- final int oomScoreOffset, int rotationOverride) throws CameraAccessException {
+ final int oomScoreOffset, int rotationOverride, boolean sharedMode)
+ throws CameraAccessException {
CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
CameraDevice device = null;
synchronized (mLock) {
@@ -1070,7 +1114,7 @@ public final class CameraManager {
characteristics,
this,
mContext.getApplicationInfo().targetSdkVersion,
- mContext, cameraDeviceSetup);
+ mContext, cameraDeviceSetup, sharedMode);
ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
try {
@@ -1091,7 +1135,7 @@ public final class CameraManager {
mContext.getApplicationInfo().targetSdkVersion,
rotationOverride,
clientAttribution,
- getDevicePolicyFromContext(mContext));
+ getDevicePolicyFromContext(mContext), sharedMode);
} catch (ServiceSpecificException e) {
if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
throw new AssertionError("Should've gone down the shim path");
@@ -1218,7 +1262,8 @@ public final class CameraManager {
@NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
throws CameraAccessException {
- openCameraImpl(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler));
+ openCameraImpl(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),
+ /*oomScoreOffset*/0, getRotationOverride(mContext), /*sharedMode*/false);
}
/**
@@ -1258,7 +1303,7 @@ public final class CameraManager {
/*oomScoreOffset*/0,
overrideToPortrait
? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT
- : ICameraService.ROTATION_OVERRIDE_NONE);
+ : ICameraService.ROTATION_OVERRIDE_NONE, /*sharedMode*/false);
}
/**
@@ -1303,9 +1348,56 @@ public final class CameraManager {
if (executor == null) {
throw new IllegalArgumentException("executor was null");
}
- openCameraImpl(cameraId, callback, executor);
+ openCameraImpl(cameraId, callback, executor, /*oomScoreOffset*/0,
+ getRotationOverride(mContext), /*sharedMode*/false);
+ }
+
+ /**
+ * Opens a shared connection to a camera with the given ID.
+ *
+ * <p>The behavior of this method matches that of
+ * {@link #openCamera(String, Executor, StateCallback)}, except that it opens the camera in
+ * shared mode where more than one client can access the camera at the same time.</p>
+ *
+ * @param cameraId The unique identifier of the camera device to open.
+ * @param executor The executor which will be used when invoking the callback.
+ * @param callback The callback which is invoked once the camera is opened
+ *
+ * @throws CameraAccessException if the camera is disabled by device policy, or is being used
+ * by a higher-priority client in non-shared mode or the device
+ * has reached its maximal resource and cannot open this camera
+ * device.
+ *
+ * @throws IllegalArgumentException if cameraId, the callback or the executor was null,
+ * or the cameraId does not match any currently or previously
+ * available camera device.
+ *
+ * @throws SecurityException if the application does not have permission to
+ * access the camera
+ *
+ * @see #getCameraIdList
+ * @see android.app.admin.DevicePolicyManager#setCameraDisabled
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.SYSTEM_CAMERA,
+ android.Manifest.permission.CAMERA,
+ })
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ public void openSharedCamera(@NonNull String cameraId,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull final CameraDevice.StateCallback callback)
+ throws CameraAccessException {
+ if (executor == null) {
+ throw new IllegalArgumentException("executor was null");
+ }
+ openCameraImpl(cameraId, callback, executor, /*oomScoreOffset*/0,
+ getRotationOverride(mContext), /*sharedMode*/true);
}
+
/**
* Open a connection to a camera with the given ID. Also specify what oom score must be offset
* by cameraserver for this client. This api can be useful for system
@@ -1372,29 +1464,35 @@ public final class CameraManager {
"oomScoreOffset < 0, cannot increase priority of camera client");
}
openCameraImpl(cameraId, callback, executor, oomScoreOffset,
- getRotationOverride(mContext));
+ getRotationOverride(mContext), /*sharedMode*/false);
}
/**
* Open a connection to a camera with the given ID, on behalf of another application.
- * Also specify the minimum oom score and process state the application
- * should have, as seen by the cameraserver.
- *
- * <p>The behavior of this method matches that of {@link #openCamera}, except that it allows
- * the caller to specify the UID to use for permission/etc verification. This can only be
- * done by services trusted by the camera subsystem to act on behalf of applications and
- * to forward the real UID.</p>
*
+ * @param cameraId
+ * The unique identifier of the camera device to open
+ * @param callback
+ * The callback which is invoked once the camera is opened
+ * @param executor
+ * The executor which will be used when invoking the callback.
* @param oomScoreOffset
* The minimum oom score that cameraservice must see for this client.
* @param rotationOverride
* The type of rotation override (none, override_to_portrait, rotation_only)
* that should be followed for this camera id connection
+ * @param sharedMode
+ * Parameter specifying if the camera should be opened in shared mode.
+ *
+ * @throws CameraAccessException if the camera is disabled by device policy,
+ * has been disconnected, or is being used by a higher-priority camera API client in
+ * non shared mode.
+ *
* @hide
*/
public void openCameraImpl(@NonNull String cameraId,
@NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
- int oomScoreOffset, int rotationOverride)
+ int oomScoreOffset, int rotationOverride, boolean sharedMode)
throws CameraAccessException {
if (cameraId == null) {
@@ -1407,24 +1505,7 @@ public final class CameraManager {
}
openCameraDeviceUserAsync(cameraId, callback, executor, oomScoreOffset,
- rotationOverride);
- }
-
- /**
- * Open a connection to a camera with the given ID, on behalf of another application.
- *
- * <p>The behavior of this method matches that of {@link #openCamera}, except that it allows
- * the caller to specify the UID to use for permission/etc verification. This can only be
- * done by services trusted by the camera subsystem to act on behalf of applications and
- * to forward the real UID.</p>
- *
- * @hide
- */
- public void openCameraImpl(@NonNull String cameraId,
- @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor)
- throws CameraAccessException {
- openCameraImpl(cameraId, callback, executor, /*oomScoreOffset*/0,
- getRotationOverride(mContext));
+ rotationOverride, sharedMode);
}
/**
@@ -2541,6 +2622,10 @@ public final class CameraManager {
}
@Override
public void onCameraClosed(String id, int deviceId) {
+ }
+ @Override
+ public void onCameraOpenedInSharedMode(String id, String clientPackageId,
+ int deviceId, boolean primaryClient) {
}};
String[] cameraIds;
@@ -3325,6 +3410,11 @@ public final class CameraManager {
}
@Override
+ public void onCameraOpenedInSharedMode(String cameraId, String clientPackageId,
+ int deviceId, boolean primaryClient) {
+ }
+
+ @Override
public void onCameraOpened(String cameraId, String clientPackageId, int deviceId) {
synchronized (mLock) {
onCameraOpenedLocked(new DeviceCameraInfo(cameraId, deviceId), clientPackageId);
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 8d36fbdc8b10..d2fcfd62bfca 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -2121,6 +2121,38 @@ public abstract class CameraMetadata<TKey> {
public static final int AUTOMOTIVE_LOCATION_EXTRA_RIGHT = 10;
//
+ // Enumeration values for CameraCharacteristics#SHARED_SESSION_COLOR_SPACE
+ //
+
+ /**
+ * @see CameraCharacteristics#SHARED_SESSION_COLOR_SPACE
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ public static final int SHARED_SESSION_COLOR_SPACE_UNSPECIFIED = -1;
+
+ /**
+ * @see CameraCharacteristics#SHARED_SESSION_COLOR_SPACE
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ public static final int SHARED_SESSION_COLOR_SPACE_SRGB = 0;
+
+ /**
+ * @see CameraCharacteristics#SHARED_SESSION_COLOR_SPACE
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ public static final int SHARED_SESSION_COLOR_SPACE_DISPLAY_P3 = 7;
+
+ /**
+ * @see CameraCharacteristics#SHARED_SESSION_COLOR_SPACE
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ public static final int SHARED_SESSION_COLOR_SPACE_BT2020_HLG = 16;
+
+ //
// Enumeration values for CaptureRequest#COLOR_CORRECTION_MODE
//
diff --git a/core/java/android/hardware/camera2/CameraSharedCaptureSession.java b/core/java/android/hardware/camera2/CameraSharedCaptureSession.java
new file mode 100644
index 000000000000..5426d4d10bec
--- /dev/null
+++ b/core/java/android/hardware/camera2/CameraSharedCaptureSession.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.view.Surface;
+
+import com.android.internal.camera.flags.Flags;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * A shared capture session for a {@link CameraDevice}, when a camera device is opened in shared
+ * mode possibly by multiple clients at the same time.
+ *
+ * <p>An active shared capture session is a special type of capture session used exclusively
+ * for shared camera access by multiple applications, provided the camera device supports this
+ * mode. To determine if a camera device supports shared mode, use the
+ * {@link android.hardware.camera2.CameraManager#isCameraDeviceSharingSupported} API.
+ * If supported, multiple clients can open the camera by calling
+ * {@link android.hardware.camera2.CameraManager#openSharedCamera} and create a shared capture
+ * session by calling {@link CameraDevice#createCaptureSession(SessionConfiguration)} and using
+ * session type as {@link android.hardware.camera2.params.SessionConfiguration#SESSION_SHARED}</p>
+ *
+ * <p>When an application has opened a camera device in shared mode, it can only create a shared
+ * capture session using session type as
+ * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_SHARED}. Any other session
+ * type value will trigger {@link IllegalArgumentException}. Once the configuration is complete and
+ * the session is ready to actually capture data, the provided
+ * {@link CameraCaptureSession.StateCallback}'s
+ * {@link CameraCaptureSession.StateCallback#onConfigured} callback will be called and will
+ * receive a CameraCaptureSession (castable to {@link CameraSharedCaptureSession}).</p>
+ *
+ * <p>Shared capture sessions uses a predefined configuration detailed in
+ * {@link CameraCharacteristics#SHARED_SESSION_CONFIGURATION}. Using different configuration values
+ * when creating session will result in an {@link IllegalArgumentException}.</p>
+ *
+ * <p>When camera is opened in shared mode, the highest priority client among all the clients will
+ * be the primary client while the others would be secondary clients. Clients will know if they are
+ * primary or secondary by the device state callback
+ * {@link CameraDevice.StateCallback#onOpenedInSharedMode}. Once the camera has been opened in
+ * shared mode, their access priorities of being a primary or secondary client can change if
+ * another higher priority client opens the camera later. Once the camera has been opened,
+ * any change in primary client status will be shared by the device state callback
+ * {@link CameraDevice.StateCallback#onClientSharedAccessPriorityChanged}.</p>
+ *
+ * <p>The priority of client access is determined by considering two factors: its current process
+ * state and its "out of memory" score. Clients operating in the background are assigned a lower
+ * priority. In contrast, clients running in the foreground, along with system-level clients, are
+ * given a higher priority.</p>
+ *
+ * <p>Primary clients can create capture requests, modify any capture parameters and send them to
+ * the capture session for a one-shot capture or as a repeating request using the following apis:
+ * </p>
+ *
+ * <ul>
+ *
+ * <li>{@link CameraSharedCaptureSession#capture}</li>
+ *
+ * <li>{@link CameraSharedCaptureSession#captureSingleRequest}</li>
+ *
+ * <li>{@link CameraSharedCaptureSession#setRepeatingRequest}</li>
+ *
+ * <li>{@link CameraSharedCaptureSession#setSingleRepeatingRequest}</li>
+ *
+ * <li>{@link CameraSharedCaptureSession#stopRepeating}</li>
+ *
+ * </ul>
+ *
+ * <p>Secondary clients cannot create a capture request and modify any capture parameters. However,
+ * they can start the camera streaming to desired surface targets using
+ * {@link CameraSharedCaptureSession#startStreaming}, which will apply default parameters. Once the
+ * streaming has successfully started, then they can stop the streaming using
+ * {@link CameraSharedCaptureSession#stopStreaming}.</p>
+ *
+ * <p>The following APIs are not supported in shared capture sessions by either the primary or
+ * secondary client.</p>
+ *
+ * <ul>
+ *
+ * <li>{@link CameraSharedCaptureSession#captureBurst}</li>
+ *
+ * <li>{@link CameraSharedCaptureSession#captureBurstRequests}</li>
+ *
+ * <li>{@link CameraSharedCaptureSession#setRepeatingBurst}</li>
+ *
+ * <li>{@link CameraSharedCaptureSession#setRepeatingBurstRequests}</li>
+ *
+ * <li>{@link CameraSharedCaptureSession#switchToOffline}</li>
+ *
+ * <li>{@link CameraSharedCaptureSession#updateOutputConfiguration}</li>
+ *
+ * <li>{@link CameraSharedCaptureSession#finalizeOutputConfigurations}</li>
+ *
+ * <li>{@link CameraSharedCaptureSession#prepare}</li>
+ *
+ * </ul>
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+@SystemApi
+public abstract class CameraSharedCaptureSession extends CameraCaptureSession {
+
+ /**
+ * Request start of the streaming of camera images by this shared capture session.
+ *
+ * <p>With this method, the camera device will continually capture images
+ * using the settings provided by primary client if there is ongoing repeating request
+ * by the primary client or default settings if no ongoing streaming request in progress.</p>
+ *
+ * <p> startStreaming has lower priority than the capture requests submitted
+ * through {@link #capture} by primary client, so if {@link #capture} is called when a
+ * streaming is active, the capture request will be processed before any further
+ * streaming requests are processed.</p>
+ *
+ * <p>To stop the streaming, call {@link #stopStreaming}</p>
+ *
+ * <p>Calling this method will replace any earlier streaming set up by this method.</p>
+ *
+ * @param surfaces List of target surfaces to use for streaming.
+ * @param executor The executor which will be used for invoking the listener.
+ * @param listener The callback object to notify the status and progress of the image capture.
+ *
+ * @return int A unique capture sequence ID used by
+ * {@link CaptureCallback#onCaptureSequenceCompleted}.
+ *
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if this session is no longer active, either because the session
+ * was explicitly closed, a new session has been created
+ * or the camera device has been closed.
+ * @throws IllegalArgumentException If the request references no surfaces or references surfaces
+ * that are not currently configured as outputs; or
+ * the executor is null, or the listener is null.
+ * @see #stopStreaming
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ @SystemApi
+ public abstract int startStreaming(@NonNull List<Surface> surfaces,
+ @NonNull @CallbackExecutor Executor executor, @NonNull CaptureCallback listener)
+ throws CameraAccessException;
+
+ /**
+ * <p>Cancel any ongoing streaming started by {@link #startStreaming}</p>
+ *
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if this session is no longer active, either because the session
+ * was explicitly closed, a new session has been created
+ * or the camera device has been closed.
+ *
+ * @see #startStreaming
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ @SystemApi
+ public abstract void stopStreaming() throws CameraAccessException;
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 84072585d7f0..ea70abb55b48 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -18,6 +18,7 @@ package android.hardware.camera2.impl;
import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.compat.CompatChanges;
@@ -41,12 +42,15 @@ import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.ICameraOfflineSession;
import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap;
import android.hardware.camera2.params.MultiResolutionStreamInfo;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.camera2.params.SharedSessionConfiguration;
+import android.hardware.camera2.params.SharedSessionConfiguration.SharedOutputConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.SubmitInfo;
import android.hardware.camera2.utils.SurfaceUtils;
@@ -188,6 +192,8 @@ public class CameraDeviceImpl extends CameraDevice
private ExecutorService mOfflineSwitchService;
private CameraOfflineSessionImpl mOfflineSessionImpl;
+ private boolean mSharedMode;
+ private boolean mIsPrimaryClient;
// Runnables for all state transitions, except error, which needs the
// error code argument
@@ -208,6 +214,25 @@ public class CameraDeviceImpl extends CameraDevice
}
};
+ private final Runnable mCallOnOpenedInSharedMode = new Runnable() {
+ @Override
+ public void run() {
+ if (!Flags.cameraMultiClient()) {
+ return;
+ }
+ StateCallbackKK sessionCallback = null;
+ synchronized (mInterfaceLock) {
+ if (mRemoteDevice == null) return; // Camera already closed
+
+ sessionCallback = mSessionStateCallback;
+ }
+ if (sessionCallback != null) {
+ sessionCallback.onOpenedInSharedMode(CameraDeviceImpl.this, mIsPrimaryClient);
+ }
+ mDeviceCallback.onOpenedInSharedMode(CameraDeviceImpl.this, mIsPrimaryClient);
+ }
+ };
+
private final Runnable mCallOnUnconfigured = new Runnable() {
@Override
public void run() {
@@ -322,6 +347,32 @@ public class CameraDeviceImpl extends CameraDevice
}
});
}
+
+ public void onOpenedInSharedMode(@NonNull CameraDevice camera, boolean primaryClient) {
+ if (!Flags.cameraMultiClient()) {
+ return;
+ }
+ mClientExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mClientStateCallback.onOpenedInSharedMode(camera, primaryClient);
+ }
+ });
+ }
+
+ public void onClientSharedAccessPriorityChanged(@NonNull CameraDevice camera,
+ boolean primaryClient) {
+ if (!Flags.cameraMultiClient()) {
+ return;
+ }
+ mClientExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mClientStateCallback.onClientSharedAccessPriorityChanged(camera, primaryClient);
+ }
+ });
+ }
+
@Override
public void onOpened(@NonNull CameraDevice camera) {
mClientExecutor.execute(new Runnable() {
@@ -358,7 +409,8 @@ public class CameraDeviceImpl extends CameraDevice
@NonNull CameraManager manager,
int appTargetSdkVersion,
Context ctx,
- @Nullable CameraDevice.CameraDeviceSetup cameraDeviceSetup) {
+ @Nullable CameraDevice.CameraDeviceSetup cameraDeviceSetup,
+ boolean sharedMode) {
if (cameraId == null || callback == null || executor == null || characteristics == null
|| manager == null) {
throw new IllegalArgumentException("Null argument given");
@@ -375,6 +427,7 @@ public class CameraDeviceImpl extends CameraDevice
mAppTargetSdkVersion = appTargetSdkVersion;
mContext = ctx;
mCameraDeviceSetup = cameraDeviceSetup;
+ mSharedMode = sharedMode;
final int MAX_TAG_LEN = 23;
String tag = String.format("CameraDevice-JV-%s", mCameraId);
@@ -438,7 +491,12 @@ public class CameraDeviceImpl extends CameraDevice
}
}
- mDeviceExecutor.execute(mCallOnOpened);
+ if (Flags.cameraMultiClient() && mSharedMode) {
+ mIsPrimaryClient = mRemoteDevice.isPrimaryClient();
+ mDeviceExecutor.execute(mCallOnOpenedInSharedMode);
+ } else {
+ mDeviceExecutor.execute(mCallOnOpened);
+ }
mDeviceExecutor.execute(mCallOnUnconfigured);
mRemoteDeviceInit = true;
@@ -576,7 +634,11 @@ public class CameraDeviceImpl extends CameraDevice
stopRepeating();
try {
- waitUntilIdle();
+ // if device is opened in shared mode, there can be multiple clients accessing the
+ // camera device. So do not wait for idle if the device is opened in shared mode.
+ if (!mSharedMode) {
+ waitUntilIdle();
+ }
mRemoteDevice.beginConfigure();
@@ -764,6 +826,54 @@ public class CameraDeviceImpl extends CameraDevice
checkAndWrapHandler(handler), operatingMode, /*sessionParams*/ null);
}
+ private boolean checkSharedOutputConfiguration(OutputConfiguration outConfig) {
+ if (!Flags.cameraMultiClient()) {
+ return false;
+ }
+ SharedSessionConfiguration sharedSessionConfiguration =
+ mCharacteristics.get(CameraCharacteristics.SHARED_SESSION_CONFIGURATION);
+ if (sharedSessionConfiguration == null) {
+ return false;
+ }
+
+ List<SharedOutputConfiguration> sharedConfigs =
+ sharedSessionConfiguration.getOutputStreamsInformation();
+ for (SharedOutputConfiguration sharedConfig : sharedConfigs) {
+ if (outConfig.getConfiguredSize().equals(sharedConfig.getSize())
+ && (outConfig.getConfiguredFormat() == sharedConfig.getFormat())
+ && (outConfig.getSurfaceGroupId() == OutputConfiguration.SURFACE_GROUP_ID_NONE)
+ && (outConfig.getSurfaceType() == sharedConfig.getSurfaceType())
+ && (outConfig.getMirrorMode() == sharedConfig.getMirrorMode())
+ && (outConfig.getUsage() == sharedConfig.getUsage())
+ && (outConfig.isReadoutTimestampEnabled()
+ == sharedConfig.isReadoutTimestampEnabled())
+ && (outConfig.getTimestampBase() == sharedConfig.getTimestampBase())
+ && (outConfig.getStreamUseCase() == sharedConfig.getStreamUseCase())
+ && (outConfig.getColorSpace().equals(
+ sharedSessionConfiguration.getColorSpace()))
+ && (outConfig.getDynamicRangeProfile()
+ == DynamicRangeProfiles.STANDARD)
+ && (outConfig.getConfiguredDataspace() == sharedConfig.getDataspace())
+ && (Objects.equals(outConfig.getPhysicalCameraId(),
+ sharedConfig.getPhysicalCameraId()))
+ && (outConfig.getSensorPixelModes().isEmpty())
+ && (!outConfig.isShared())) {
+ //Found valid config, return true
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkSharedSessionConfiguration(List<OutputConfiguration> outputConfigs) {
+ for (OutputConfiguration out : outputConfigs) {
+ if (!checkSharedOutputConfiguration(out)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
@Override
public void createCaptureSession(SessionConfiguration config)
throws CameraAccessException {
@@ -778,6 +888,14 @@ public class CameraDeviceImpl extends CameraDevice
if (config.getExecutor() == null) {
throw new IllegalArgumentException("Invalid executor");
}
+ if (mSharedMode) {
+ if (config.getSessionType() != SessionConfiguration.SESSION_SHARED) {
+ throw new IllegalArgumentException("Invalid session type");
+ }
+ if (!checkSharedSessionConfiguration(outputConfigs)) {
+ throw new IllegalArgumentException("Invalid output configurations");
+ }
+ }
createCaptureSessionInternal(config.getInputConfiguration(), outputConfigs,
config.getStateCallback(), config.getExecutor(), config.getSessionType(),
config.getSessionParameters());
@@ -801,6 +919,11 @@ public class CameraDeviceImpl extends CameraDevice
throw new IllegalArgumentException("Constrained high speed session doesn't support"
+ " input configuration yet.");
}
+ boolean isSharedSession = (operatingMode == ICameraDeviceUser.SHARED_MODE);
+ if (isSharedSession && inputConfig != null) {
+ throw new IllegalArgumentException("Shared capture session doesn't support"
+ + " input configuration yet.");
+ }
if (mCurrentExtensionSession != null) {
mCurrentExtensionSession.commitStats();
@@ -860,6 +983,10 @@ public class CameraDeviceImpl extends CameraDevice
newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++,
callback, executor, this, mDeviceExecutor, configureSuccess,
mCharacteristics);
+ } else if (isSharedSession) {
+ newSession = new CameraSharedCaptureSessionImpl(mNextSessionId++,
+ callback, executor, this, mDeviceExecutor, configureSuccess,
+ mIsPrimaryClient);
} else {
newSession = new CameraCaptureSessionImpl(mNextSessionId++, input,
callback, executor, this, mDeviceExecutor, configureSuccess);
@@ -1882,6 +2009,40 @@ public class CameraDeviceImpl extends CameraDevice
}
}
+ /**
+ * Callback when client access priorities change when camera is opened in shared mode.
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ public void onClientSharedAccessPriorityChanged(boolean primaryClient) {
+ if (DEBUG) {
+ Log.d(TAG, String.format(
+ "onClientSharedAccessPriorityChanged received, primary client = "
+ + primaryClient));
+ }
+ synchronized (mInterfaceLock) {
+ if (mRemoteDevice == null && mRemoteDeviceInit) {
+ return; // Camera already closed, user is not interested in this callback anymore.
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mDeviceExecutor.execute(obtainRunnable(
+ CameraDeviceImpl::notifyClientSharedAccessPriorityChanged, this,
+ primaryClient).recycleOnUse());
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ private void notifyClientSharedAccessPriorityChanged(boolean primaryClient) {
+ if (!CameraDeviceImpl.this.isClosed()) {
+ mIsPrimaryClient = primaryClient;
+ mDeviceCallback.onClientSharedAccessPriorityChanged(CameraDeviceImpl.this,
+ primaryClient);
+ }
+ }
+
public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) {
if (DEBUG) {
Log.d(TAG, String.format(
@@ -2447,6 +2608,12 @@ public class CameraDeviceImpl extends CameraDevice
}
@Override
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ public void onClientSharedAccessPriorityChanged(boolean primaryClient) {
+ CameraDeviceImpl.this.onClientSharedAccessPriorityChanged(primaryClient);
+ }
+
+ @Override
public void onPrepared(int streamId) {
final OutputConfiguration output;
final StateCallbackKK sessionCallback;
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 1cc085658bfa..c0a5928a369b 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -63,6 +63,7 @@ import android.hardware.camera2.params.OisSample;
import android.hardware.camera2.params.RecommendedStreamConfiguration;
import android.hardware.camera2.params.RecommendedStreamConfigurationMap;
import android.hardware.camera2.params.ReprocessFormatsMap;
+import android.hardware.camera2.params.SharedSessionConfiguration;
import android.hardware.camera2.params.StreamConfiguration;
import android.hardware.camera2.params.StreamConfigurationDuration;
import android.hardware.camera2.params.StreamConfigurationMap;
@@ -866,6 +867,15 @@ public class CameraMetadataNative implements Parcelable {
return (T) metadata.getLensIntrinsicSamples();
}
});
+ sGetCommandMap.put(
+ CameraCharacteristics.SHARED_SESSION_CONFIGURATION.getNativeKey(),
+ new GetCommand() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+ return (T) metadata.getSharedSessionConfiguration();
+ }
+ });
}
private int[] getAvailableFormats() {
@@ -1658,6 +1668,22 @@ public class CameraMetadataNative implements Parcelable {
listHighResolution);
}
+ private SharedSessionConfiguration getSharedSessionConfiguration() {
+ if (!Flags.cameraMultiClient()) {
+ return null;
+ }
+ Integer sharedSessionColorSpace = getBase(
+ CameraCharacteristics.SHARED_SESSION_COLOR_SPACE);
+ long[] sharedOutputConfigurations = getBase(
+ CameraCharacteristics.SHARED_SESSION_OUTPUT_CONFIGURATIONS);
+
+ if ((sharedSessionColorSpace == null) || (sharedOutputConfigurations == null)) {
+ return null;
+ }
+
+ return new SharedSessionConfiguration(sharedSessionColorSpace, sharedOutputConfigurations);
+ }
+
private StreamConfigurationMap getStreamConfigurationMapMaximumResolution() {
StreamConfiguration[] configurations = getBase(
CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
diff --git a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
index eb2ff88ec1b2..1769c4638805 100644
--- a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
@@ -16,11 +16,13 @@
package android.hardware.camera2.impl;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.FlaggedApi;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraOfflineSession;
import android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallback;
import android.hardware.camera2.CaptureFailure;
@@ -40,15 +42,15 @@ import android.util.Range;
import android.util.SparseArray;
import android.view.Surface;
+import com.android.internal.camera.flags.Flags;
+
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.Executor;
-
-import static com.android.internal.util.Preconditions.*;
+import java.util.concurrent.atomic.AtomicBoolean;
public class CameraOfflineSessionImpl extends CameraOfflineSession
implements IBinder.DeathRecipient {
@@ -176,6 +178,12 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession
}
@Override
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ public void onClientSharedAccessPriorityChanged(boolean primaryClient) {
+ Log.v(TAG, "onClientSharedAccessPriorityChanged primaryClient = " + primaryClient);
+ }
+
+ @Override
public void onDeviceIdle() {
synchronized(mInterfaceLock) {
if (mRemoteSession == null) {
diff --git a/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java
new file mode 100644
index 000000000000..a1f31c0ced5e
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java
@@ -0,0 +1,242 @@
+/*
+ * 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.hardware.camera2.impl;
+
+import android.annotation.FlaggedApi;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraSharedCaptureSession;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.view.Surface;
+
+import com.android.internal.camera.flags.Flags;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Standard implementation of CameraSharedCaptureSession.
+ *
+ * <p>
+ * Mostly just forwards calls to an instance of CameraCaptureSessionImpl,
+ * but implements the few necessary behavior changes and additional methods required
+ * for the shared session mode.
+ * </p>
+ */
+@FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+public class CameraSharedCaptureSessionImpl
+ extends CameraSharedCaptureSession implements CameraCaptureSessionCore {
+ private static final String TAG = "CameraSharedCaptureSessionImpl";
+ private final CameraCaptureSessionImpl mSessionImpl;
+ private final ConditionVariable mInitialized = new ConditionVariable();
+ private boolean mIsPrimary;
+
+ /**
+ * Create a new CameraCaptureSession.
+ */
+ CameraSharedCaptureSessionImpl(int id,
+ CameraCaptureSession.StateCallback callback, Executor stateExecutor,
+ android.hardware.camera2.impl.CameraDeviceImpl deviceImpl,
+ Executor deviceStateExecutor, boolean configureSuccess, boolean isPrimary) {
+ CameraCaptureSession.StateCallback wrapperCallback = new WrapperCallback(callback);
+ mSessionImpl = new CameraCaptureSessionImpl(id, /*input*/null, wrapperCallback,
+ stateExecutor, deviceImpl, deviceStateExecutor, configureSuccess);
+ mIsPrimary = isPrimary;
+ mInitialized.open();
+ }
+
+ @Override
+ public int startStreaming(List<Surface> surfaces, Executor executor, CaptureCallback listener)
+ throws CameraAccessException {
+ // Todo: Need to add implementation.
+ return 0;
+ }
+
+ @Override
+ public void stopStreaming() throws CameraAccessException {
+ // Todo: Need to add implementation.
+ }
+
+ @Override
+ public void close() {
+ mSessionImpl.close();
+ }
+
+ @Override
+ public Surface getInputSurface() {
+ return null;
+ }
+
+ @Override
+ public boolean isReprocessable() {
+ return false;
+ }
+
+ @Override
+ public void abortCaptures() throws CameraAccessException {
+ if (mIsPrimary) {
+ mSessionImpl.abortCaptures();
+ }
+ }
+
+ @Override
+ public int setRepeatingRequest(CaptureRequest request, CaptureCallback listener,
+ Handler handler) throws CameraAccessException {
+ if (mIsPrimary) {
+ return mSessionImpl.setRepeatingRequest(request, listener, handler);
+ }
+ throw new UnsupportedOperationException("Shared capture session only supports this method"
+ + " for primary clients");
+ }
+
+ @Override
+ public void stopRepeating() throws CameraAccessException {
+ if (mIsPrimary) {
+ mSessionImpl.stopRepeating();
+ }
+ }
+
+ @Override
+ public int capture(CaptureRequest request, CaptureCallback listener, Handler handler)
+ throws CameraAccessException {
+ if (mIsPrimary) {
+ return mSessionImpl.capture(request, listener, handler);
+ }
+ throw new UnsupportedOperationException("Shared capture session only supports this method"
+ + " for primary clients");
+ }
+
+ @Override
+ public void tearDown(Surface surface) throws CameraAccessException {
+ mSessionImpl.tearDown(surface);
+ }
+
+ @Override
+ public CameraDevice getDevice() {
+ return mSessionImpl.getDevice();
+ }
+
+ @Override
+ public boolean isAborting() {
+ return mSessionImpl.isAborting();
+ }
+
+ @Override
+ public CameraDeviceImpl.StateCallbackKK getDeviceStateCallback() {
+ return mSessionImpl.getDeviceStateCallback();
+ }
+
+ @Override
+ public void replaceSessionClose() {
+ mSessionImpl.replaceSessionClose();
+ }
+
+ @Override
+ public int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback listener,
+ Handler handler) throws CameraAccessException {
+ throw new UnsupportedOperationException("Shared Capture session doesn't support"
+ + " this method");
+ }
+
+ @Override
+ public int captureBurst(List<CaptureRequest> requests, CaptureCallback listener,
+ Handler handler) throws CameraAccessException {
+ throw new UnsupportedOperationException("Shared Capture session doesn't support"
+ + " this method");
+ }
+
+ @Override
+ public void updateOutputConfiguration(OutputConfiguration config)
+ throws CameraAccessException {
+ throw new UnsupportedOperationException("Shared capture session doesn't support"
+ + " this method");
+ }
+
+ @Override
+ public void finalizeOutputConfigurations(List<OutputConfiguration> deferredOutputConfigs)
+ throws CameraAccessException {
+ throw new UnsupportedOperationException("Shared capture session doesn't support"
+ + " this method");
+ }
+
+ @Override
+ public void prepare(Surface surface) throws CameraAccessException {
+ throw new UnsupportedOperationException("Shared capture session doesn't support"
+ + " this method");
+ }
+
+ @Override
+ public void prepare(int maxCount, Surface surface) throws CameraAccessException {
+ throw new UnsupportedOperationException("Shared capture session doesn't support"
+ + " this method");
+ }
+
+ @Override
+ public void closeWithoutDraining() {
+ throw new UnsupportedOperationException("Shared capture session doesn't support"
+ + " this method");
+ }
+
+ private class WrapperCallback extends StateCallback {
+ private final StateCallback mCallback;
+
+ WrapperCallback(StateCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onConfigured(CameraCaptureSession session) {
+ mInitialized.block();
+ mCallback.onConfigured(CameraSharedCaptureSessionImpl.this);
+ }
+
+ @Override
+ public void onConfigureFailed(CameraCaptureSession session) {
+ mInitialized.block();
+ mCallback.onConfigureFailed(CameraSharedCaptureSessionImpl.this);
+ }
+
+ @Override
+ public void onReady(CameraCaptureSession session) {
+ mCallback.onReady(CameraSharedCaptureSessionImpl.this);
+ }
+
+ @Override
+ public void onActive(CameraCaptureSession session) {
+ mCallback.onActive(CameraSharedCaptureSessionImpl.this);
+ }
+
+ @Override
+ public void onCaptureQueueEmpty(CameraCaptureSession session) {
+ mCallback.onCaptureQueueEmpty(CameraSharedCaptureSessionImpl.this);
+ }
+
+ @Override
+ public void onClosed(CameraCaptureSession session) {
+ mCallback.onClosed(CameraSharedCaptureSessionImpl.this);
+ }
+
+ @Override
+ public void onSurfacePrepared(CameraCaptureSession session, Surface surface) {
+ mCallback.onSurfacePrepared(CameraSharedCaptureSessionImpl.this,
+ surface);
+ }
+ }
+}
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index aec2cff61d99..831c75ec5d33 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -301,4 +301,16 @@ public class ICameraDeviceUserWrapper {
}
}
+ /**
+ * API to check if the client is primary client when camera device is opened in shared mode.
+ */
+ public boolean isPrimaryClient() throws CameraAccessException {
+ try {
+ return mRemoteDevice.isPrimaryClient();
+ } catch (ServiceSpecificException e) {
+ throw ExceptionUtils.throwAsPublicException(e);
+ } catch (RemoteException e) {
+ throw ExceptionUtils.throwAsPublicException(e);
+ }
+ }
}
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index d38be9b7b694..e12c46322d8c 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -29,6 +29,7 @@ import android.annotation.TestApi;
import android.graphics.ColorSpace;
import android.graphics.ImageFormat;
import android.graphics.ImageFormat.Format;
+import android.hardware.DataSpace.NamedDataSpace;
import android.hardware.HardwareBuffer;
import android.hardware.HardwareBuffer.Usage;
import android.hardware.camera2.CameraCaptureSession;
@@ -1729,6 +1730,79 @@ public final class OutputConfiguration implements Parcelable {
}
/**
+ * Get the configured format associated with this {@link OutputConfiguration}.
+ *
+ * @return {@link android.graphics.ImageFormat#Format} associated with this
+ * {@link OutputConfiguration}.
+ *
+ * @hide
+ */
+ public @Format int getConfiguredFormat() {
+ return mConfiguredFormat;
+ }
+
+ /**
+ * Get the usage flag associated with this {@link OutputConfiguration}.
+ *
+ * @return {@link HardwareBuffer#Usage} associated with this {@link OutputConfiguration}.
+ *
+ * @hide
+ */
+ public @Usage long getUsage() {
+ return mUsage;
+ }
+
+ /**
+ * Get the surface type associated with this {@link OutputConfiguration}.
+ *
+ * @return The surface type associated with this {@link OutputConfiguration}.
+ *
+ * @see #SURFACE_TYPE_SURFACE_VIEW
+ * @see #SURFACE_TYPE_SURFACE_TEXTURE
+ * @see #SURFACE_TYPE_MEDIA_RECORDER
+ * @see #SURFACE_TYPE_MEDIA_CODEC
+ * @see #SURFACE_TYPE_IMAGE_READER
+ * @see #SURFACE_TYPE_UNKNOWN
+ * @hide
+ */
+ public int getSurfaceType() {
+ return mSurfaceType;
+ }
+
+ /**
+ * Get the sensor pixel modes associated with this {@link OutputConfiguration}.
+ *
+ * @return List of {@link #SensorPixelMode} associated with this {@link OutputConfiguration}.
+ *
+ * @hide
+ */
+ public @NonNull List<Integer> getSensorPixelModes() {
+ return mSensorPixelModesUsed;
+ }
+
+ /**
+ * Get the sharing mode associated with this {@link OutputConfiguration}.
+ *
+ * @return true if surface sharing is enabled with this {@link OutputConfiguration}.
+ *
+ * @hide
+ */
+ public boolean isShared() {
+ return mIsShared;
+ }
+
+ /**
+ * Get the dataspace associated with this {@link OutputConfiguration}.
+ *
+ * @return {@link Dataspace#NamedDataSpace} for this {@link OutputConfiguration}.
+ *
+ * @hide
+ */
+ public @NamedDataSpace int getConfiguredDataspace() {
+ return mConfiguredDataspace;
+ }
+
+ /**
* Get the physical camera ID associated with this {@link OutputConfiguration}.
*
* <p>If this OutputConfiguration isn't targeting a physical camera of a logical
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
index 50c6b5b8b995..82aa64b1474c 100644
--- a/core/java/android/hardware/camera2/params/SessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -23,6 +23,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.graphics.ColorSpace;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
@@ -78,6 +79,19 @@ public final class SessionConfiguration implements Parcelable {
CameraDevice.SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED;
/**
+ * A shared session type containing instances of {@link OutputConfiguration} from a set of
+ * predefined stream configurations. A shared session can be shared among multiple clients.
+ * Shared session does not have any {@link InputConfiguration} as it does not support
+ * reprocessable sessions.
+ *
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+ @SystemApi
+ public static final int SESSION_SHARED = CameraDevice.SESSION_OPERATION_MODE_SHARED;
+
+ /**
* First vendor-specific session mode
* @hide
*/
diff --git a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java
new file mode 100644
index 000000000000..cdcc92ce4404
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java
@@ -0,0 +1,312 @@
+/*
+ * 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.hardware.camera2.params;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.graphics.ColorSpace;
+import android.graphics.ImageFormat.Format;
+import android.hardware.DataSpace.NamedDataSpace;
+import android.hardware.HardwareBuffer.Usage;
+import android.hardware.camera2.params.OutputConfiguration.MirrorMode;
+import android.hardware.camera2.params.OutputConfiguration.StreamUseCase;
+import android.hardware.camera2.params.OutputConfiguration.TimestampBase;
+import android.util.Log;
+import android.util.Size;
+
+import com.android.internal.camera.flags.Flags;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Immutable class to store the shared session configuration
+ * {@link CameraCharacteristics#SHARED_SESSION_CONFIGURATION} to set up
+ * {@link android.view.Surface Surfaces} for creating a
+ * {@link android.hardware.camera2.CameraSharedCaptureSession capture session} using
+ * {@link android.hardware.camera2.CameraDevice#createCaptureSession(SessionConfiguration)} and
+ * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_SHARED
+ * shared capture session} when camera has been opened in shared mode using
+ * {@link #openSharedCamera(String, Executor, StateCallback)}.
+ *
+ * <p>This is the authoritative list for all output configurations that are supported by a camera
+ * device when opened in shared mode.</p>
+ *
+ * <p>An instance of this object is available from {@link CameraCharacteristics} using
+ * the {@link CameraCharacteristics#SHARED_SESSION_CONFIGURATION} key and the
+ * {@link CameraCharacteristics#get} method.</p>
+ *
+ * <pre><code>{@code
+ * CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
+ * StreamConfigurationMap configs = characteristics.get(
+ * CameraCharacteristics.SHARED_SESSION_CONFIGURATION);
+ * }</code></pre>
+ *
+ * @see CameraCharacteristics#SHARED_SESSION_CONFIGURATION
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
+ * @see SessionConfiguration#SESSION_SHARED
+ * @see CameraManager#openSharedCamera(String, Executor, StateCallback)
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT)
+public final class SharedSessionConfiguration {
+ private static final String TAG = "SharedSessionConfiguration";
+ // Metadata android.info.availableSharedOutputConfigurations has list of shared output
+ // configurations. Each output configuration has minimum of 11 entries of size long
+ // followed by the physical camera id if present.
+ // See android.info.availableSharedOutputConfigurations for details.
+ private static final int SHARED_OUTPUT_CONFIG_NUM_OF_ENTRIES = 11;
+ /**
+ * Immutable class to store shared output stream information.
+ */
+ public static final class SharedOutputConfiguration {
+ private final int mSurfaceType;
+ private final Size mSize;
+ private final int mFormat;
+ private final int mDataspace;
+ private final long mStreamUseCase;
+ private String mPhysicalCameraId;
+ private final long mUsage;
+ private int mTimestampBase;
+ private int mMirrorMode;
+ private boolean mReadoutTimestampEnabled;
+
+ /**
+ * Create a new {@link SharedOutputConfiguration}.
+ *
+ * @param surfaceType Surface Type for this output configuration.
+ * @param sz Size for this output configuration.
+ * @param format {@link android.graphics.ImageFormat#Format} associated with this
+ * {@link OutputConfiguration}.
+ * @param mirrorMode {@link OutputConfiguration#MirrorMode} for this output configuration.
+ * @param readoutTimeStampEnabled Flag indicating whether readout timestamp is enabled
+ * for this output configuration.
+ * @param timestampBase {@link OutputConfiguration#TimestampBase} for this output
+ * configuration.
+ * @param dataspace {@link Dataspace#NamedDataSpace} for this output configuration.
+ * @param usage {@link HardwareBuffer#Usage} for this output configuration.
+ * @param streamUseCase {@link OutputConfiguration#StreamUseCase} for this output
+ * configuration.
+ * @param physicalCamId Physical Camera Id for this output configuration.
+ *
+ * @hide
+ */
+ public SharedOutputConfiguration(int surfaceType, @NonNull Size sz, @Format int format,
+ @MirrorMode int mirrorMode, boolean readoutTimeStampEnabled,
+ @TimestampBase int timestampBase, @NamedDataSpace int dataspace, @Usage long usage,
+ @StreamUseCase long streamUseCase, @Nullable String physicalCamId) {
+ mSurfaceType = surfaceType;
+ mSize = sz;
+ mFormat = format;
+ mMirrorMode = mirrorMode;
+ mReadoutTimestampEnabled = readoutTimeStampEnabled;
+ mTimestampBase = timestampBase;
+ mDataspace = dataspace;
+ mUsage = usage;
+ mStreamUseCase = streamUseCase;
+ mPhysicalCameraId = physicalCamId;
+ }
+
+ /**
+ * Returns the surface type configured for the shared output configuration.
+ * @return SURFACE_TYPE_UNKNOWN = -1
+ * SURFACE_TYPE_SURFACE_VIEW = 0
+ * SURFACE_TYPE_SURFACE_TEXTURE = 1
+ * SURFACE_TYPE_MEDIA_RECORDER = 2
+ * SURFACE_TYPE_MEDIA_CODEC = 3
+ * SURFACE_TYPE_IMAGE_READER = 4
+ */
+ public int getSurfaceType() {
+ return mSurfaceType;
+ }
+
+ /**
+ * Returns the format of the shared output configuration.
+ * @return format The format of the configured output. This must be one of the
+ * {@link android.graphics.ImageFormat} or {@link android.graphics.PixelFormat}
+ * constants. Note that not all formats are supported by the camera device.
+ */
+ public @Format int getFormat() {
+ return mFormat;
+ }
+
+ /**
+ * Returns the configured size for the shared output configuration.
+ * @return surfaceSize Size for the shared output configuration
+ *
+ */
+ public @NonNull Size getSize() {
+ return mSize;
+ }
+
+ /**
+ * Return datatspace configured for the shared output configuration.
+ *
+ * @return {@link Dataspace#NamedDataSpace} configured for shared session
+ */
+ public @NamedDataSpace int getDataspace() {
+ return mDataspace;
+ }
+
+ /**
+ * Get the mirroring mode configured for the shared output configuration.
+ *
+ * @return {@link OutputConfiguration#MirrorMode} configured for the shared session
+ */
+ public @MirrorMode int getMirrorMode() {
+ return mMirrorMode;
+ }
+
+ /**
+ * Get the stream use case configured for the shared output configuration.
+ *
+ * @return {@link OutputConfiguration#StreamUseCase} configured for the shared session
+ */
+ public @StreamUseCase long getStreamUseCase() {
+ return mStreamUseCase;
+ }
+
+ /**
+ * Get the timestamp base configured for the shared output configuration.
+ *
+ * @return {@link OutputConfiguration#TimestampBase} configured for the shared session
+ */
+ public @TimestampBase int getTimestampBase() {
+ return mTimestampBase;
+ }
+
+ /** Whether readout timestamp is used for this shared output configuration.
+ *
+ */
+ public boolean isReadoutTimestampEnabled() {
+ return mReadoutTimestampEnabled;
+ }
+
+ /** Returns the usage if set for this shared output configuration.
+ *
+ * @return {@link HardwareBuffer#Usage} flags if set for shared output configuration with
+ * the ImageReader output surface.
+ */
+ public @Usage long getUsage() {
+ return mUsage;
+ }
+
+ public @Nullable String getPhysicalCameraId() {
+ return mPhysicalCameraId;
+ }
+ }
+
+ /**
+ * Create a new {@link SharedSessionConfiguration}.
+ *
+ * <p>The array parameters ownership is passed to this object after creation; do not
+ * write to them after this constructor is invoked.</p>
+ *
+ * @param sharedColorSpace the colorspace to be used for the shared output configurations.
+ * @param sharedOutputConfigurations a non-{@code null} array of metadata
+ * android.info.availableSharedOutputConfigurations
+ *
+ * @hide
+ */
+ public SharedSessionConfiguration(int sharedColorSpace,
+ @NonNull long[] sharedOutputConfigurations) {
+ mColorSpace = sharedColorSpace;
+ byte physicalCameraIdLen;
+ int surfaceType, width, height, format, mirrorMode, timestampBase, dataspace;
+ long usage, streamUseCase;
+ boolean isReadOutTimestampEnabled;
+ // Parse metadata android.info.availableSharedOutputConfigurations which contains
+ // list of shared output configurations.
+ int numOfEntries = sharedOutputConfigurations.length;
+ int i = 0;
+ while (numOfEntries >= SharedSessionConfiguration.SHARED_OUTPUT_CONFIG_NUM_OF_ENTRIES) {
+ surfaceType = (int) sharedOutputConfigurations[i];
+ width = (int) sharedOutputConfigurations[i + 1];
+ height = (int) sharedOutputConfigurations[i + 2];
+ format = (int) sharedOutputConfigurations[i + 3];
+ mirrorMode = (int) sharedOutputConfigurations[i + 4];
+ isReadOutTimestampEnabled = (sharedOutputConfigurations[i + 5] != 0);
+ timestampBase = (int) sharedOutputConfigurations[i + 6];
+ dataspace = (int) sharedOutputConfigurations[i + 7];
+ usage = sharedOutputConfigurations[i + 8];
+ streamUseCase = sharedOutputConfigurations[i + 9];
+ physicalCameraIdLen = (byte) sharedOutputConfigurations[i + 10];
+ numOfEntries -= SharedSessionConfiguration.SHARED_OUTPUT_CONFIG_NUM_OF_ENTRIES;
+ i += SharedSessionConfiguration.SHARED_OUTPUT_CONFIG_NUM_OF_ENTRIES;
+ if (numOfEntries < physicalCameraIdLen) {
+ Log.e(TAG, "Number of remaining data in shared configuration is less than"
+ + " physical camera id length . Malformed metadata"
+ + " android.info.availableSharedOutputConfigurations.");
+ break;
+ }
+ StringBuilder physicalCameraId = new StringBuilder();
+ long asciiValue;
+ for (int j = 0; j < physicalCameraIdLen; j++) {
+ asciiValue = sharedOutputConfigurations[i + j];
+ if (asciiValue == 0) { // Check for null terminator
+ break;
+ }
+ physicalCameraId.append((char) asciiValue);
+ }
+ SharedOutputConfiguration outputInfo;
+ outputInfo = new SharedOutputConfiguration(surfaceType, new Size(width, height),
+ format, mirrorMode, isReadOutTimestampEnabled, timestampBase,
+ dataspace, usage, streamUseCase, physicalCameraId.toString());
+ mOutputStreamConfigurations.add(outputInfo);
+ i += physicalCameraIdLen;
+ numOfEntries -= physicalCameraIdLen;
+ }
+ if (numOfEntries != 0) {
+ Log.e(TAG, "Unexpected entries left in shared output configuration."
+ + " Malformed metadata android.info.availableSharedOutputConfigurations.");
+ }
+ }
+
+ /**
+ * Return the shared session color space which is configured.
+ *
+ * @return the shared session color space
+ */
+ @SuppressLint("MethodNameUnits")
+ public @Nullable ColorSpace getColorSpace() {
+ if (mColorSpace != ColorSpaceProfiles.UNSPECIFIED) {
+ return ColorSpace.get(ColorSpace.Named.values()[mColorSpace]);
+ } else {
+ return null;
+ }
+ }
+ /**
+ * Get information about each shared output configuarion in the shared session.
+ *
+ * @return Non-modifiable list of output configuration.
+ *
+ */
+ public @NonNull List<SharedOutputConfiguration> getOutputStreamsInformation() {
+ return Collections.unmodifiableList(mOutputStreamConfigurations);
+ }
+
+ private int mColorSpace;
+ private final ArrayList<SharedOutputConfiguration> mOutputStreamConfigurations =
+ new ArrayList<SharedOutputConfiguration>();
+}
+
diff --git a/core/java/android/hardware/contexthub/HubDiscoveryInfo.java b/core/java/android/hardware/contexthub/HubDiscoveryInfo.java
new file mode 100644
index 000000000000..875c4b4182be
--- /dev/null
+++ b/core/java/android/hardware/contexthub/HubDiscoveryInfo.java
@@ -0,0 +1,56 @@
+/*
+ * 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.hardware.contexthub;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+import android.hardware.location.ContextHubManager;
+
+/**
+ * Class that represents the result of from an hub endpoint discovery.
+ *
+ * <p>The type is returned from an endpoint discovery query via {@link
+ * ContextHubManager#findEndpoints}. Application may use the values {@link #getHubEndpointInfo} to
+ * retrieve the {@link HubEndpointInfo} that describes the endpoint that matches the query. The
+ * class provides flexibility in returning more information (e.g. service provided by the endpoint)
+ * in addition to the information about the endpoint.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public class HubDiscoveryInfo {
+ // TODO(b/375487784): Add ServiceInfo to the result.
+ android.hardware.contexthub.HubEndpointInfo mEndpointInfo;
+
+ /**
+ * Constructor for internal use.
+ *
+ * @hide
+ */
+ public HubDiscoveryInfo(android.hardware.contexthub.HubEndpointInfo endpointInfo) {
+ mEndpointInfo = endpointInfo;
+ }
+
+ /** Get the {@link android.hardware.contexthub.HubEndpointInfo} for the endpoint found. */
+ @NonNull
+ public HubEndpointInfo getHubEndpointInfo() {
+ return mEndpointInfo;
+ }
+}
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
new file mode 100644
index 000000000000..99b05da08b0b
--- /dev/null
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -0,0 +1,406 @@
+/*
+ * 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.hardware.contexthub;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+import android.content.Context;
+import android.hardware.location.IContextHubService;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.GuardedBy;
+
+import java.util.concurrent.Executor;
+
+/**
+ * An object representing an endpoint exposed to ContextHub and VendorHub. The object encapsulates
+ * the lifecycle and message callbacks for an endpoint.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public class HubEndpoint {
+ private static final String TAG = "HubEndpoint";
+
+ private final Object mLock = new Object();
+ private final HubEndpointInfo mPendingHubEndpointInfo;
+ @Nullable private final IHubEndpointLifecycleCallback mLifecycleCallback;
+ @NonNull private final Executor mLifecycleCallbackExecutor;
+
+ @GuardedBy("mLock")
+ private final SparseArray<HubEndpointSession> mActiveSessions = new SparseArray<>();
+
+ private final IContextHubEndpointCallback mServiceCallback =
+ new IContextHubEndpointCallback.Stub() {
+ @Override
+ public void onSessionOpenRequest(int sessionId, HubEndpointInfo initiator)
+ throws RemoteException {
+ HubEndpointSession activeSession;
+ synchronized (mLock) {
+ activeSession = mActiveSessions.get(sessionId);
+ // TODO(b/378974199): Consider refactor these assertions
+ if (activeSession != null) {
+ Log.i(
+ TAG,
+ "onSessionOpenComplete: session already exists, id="
+ + sessionId);
+ return;
+ }
+ }
+
+ if (mLifecycleCallback != null) {
+ mLifecycleCallbackExecutor.execute(
+ () ->
+ processSessionOpenRequestResult(
+ sessionId,
+ initiator,
+ mLifecycleCallback.onSessionOpenRequest(
+ initiator)));
+ }
+ }
+
+ private void processSessionOpenRequestResult(
+ int sessionId, HubEndpointInfo initiator, HubEndpointSessionResult result) {
+ if (result == null) {
+ throw new IllegalArgumentException(
+ "HubEndpointSessionResult shouldn't be null.");
+ }
+
+ if (result.isAccepted()) {
+ acceptSession(sessionId, initiator);
+ } else {
+ Log.i(
+ TAG,
+ "Session "
+ + sessionId
+ + " from "
+ + initiator
+ + " was rejected, reason="
+ + result.getReason());
+ rejectSession(sessionId);
+ }
+ }
+
+ private void acceptSession(int sessionId, HubEndpointInfo initiator) {
+ if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
+ // No longer registered?
+ return;
+ }
+
+ // Retrieve the active session
+ HubEndpointSession activeSession;
+ synchronized (mLock) {
+ activeSession = mActiveSessions.get(sessionId);
+ // TODO(b/378974199): Consider refactor these assertions
+ if (activeSession != null) {
+ Log.e(
+ TAG,
+ "onSessionOpenRequestResult: session already exists, id="
+ + sessionId);
+ return;
+ }
+
+ activeSession =
+ new HubEndpointSession(
+ sessionId,
+ HubEndpoint.this,
+ mAssignedHubEndpointInfo,
+ initiator);
+ try {
+ // oneway call to notify system service that the request is completed
+ mServiceToken.openSessionRequestComplete(sessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onSessionOpenRequestResult: ", e);
+ return;
+ }
+
+ mActiveSessions.put(sessionId, activeSession);
+ }
+
+ // Execute the callback
+ activeSession.setOpened();
+ if (mLifecycleCallback != null) {
+ final HubEndpointSession finalActiveSession = activeSession;
+ mLifecycleCallbackExecutor.execute(
+ () -> mLifecycleCallback.onSessionOpened(finalActiveSession));
+ }
+ }
+
+ private void rejectSession(int sessionId) {
+ if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
+ // No longer registered?
+ return;
+ }
+
+ try {
+ mServiceToken.closeSession(
+ sessionId,
+ IHubEndpointLifecycleCallback
+ .REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onSessionOpenComplete(int sessionId) throws RemoteException {
+ final HubEndpointSession activeSession;
+
+ // Retrieve the active session
+ synchronized (mLock) {
+ activeSession = mActiveSessions.get(sessionId);
+ }
+ // TODO(b/378974199): Consider refactor these assertions
+ if (activeSession == null) {
+ Log.i(
+ TAG,
+ "onSessionOpenComplete: no pending session open request? id="
+ + sessionId);
+ return;
+ }
+
+ // Execute the callback
+ activeSession.setOpened();
+ if (mLifecycleCallback != null) {
+ mLifecycleCallbackExecutor.execute(
+ () -> mLifecycleCallback.onSessionOpened(activeSession));
+ }
+ }
+
+ @Override
+ public void onSessionClosed(int sessionId, int reason) throws RemoteException {
+ final HubEndpointSession activeSession;
+
+ // Retrieve the active session
+ synchronized (mLock) {
+ activeSession = mActiveSessions.get(sessionId);
+ }
+ // TODO(b/378974199): Consider refactor these assertions
+ if (activeSession == null) {
+ Log.i(TAG, "onSessionClosed: session not active, id=" + sessionId);
+ return;
+ }
+
+ // Execute the callback
+ if (mLifecycleCallback != null) {
+ mLifecycleCallbackExecutor.execute(
+ () -> {
+ mLifecycleCallback.onSessionClosed(activeSession, reason);
+
+ // Remove the session object first to call
+ activeSession.setClosed();
+ synchronized (mLock) {
+ mActiveSessions.remove(sessionId);
+ }
+ });
+ }
+ }
+ };
+
+ /** Binder returned from system service, non-null while registered. */
+ @Nullable private IContextHubEndpoint mServiceToken;
+
+ /** HubEndpointInfo with the assigned endpoint id from system service. */
+ @Nullable private HubEndpointInfo mAssignedHubEndpointInfo;
+
+ private HubEndpoint(
+ @NonNull HubEndpointInfo pendingEndpointInfo,
+ @Nullable IHubEndpointLifecycleCallback endpointLifecycleCallback,
+ @NonNull Executor lifecycleCallbackExecutor) {
+ mPendingHubEndpointInfo = pendingEndpointInfo;
+ mLifecycleCallback = endpointLifecycleCallback;
+ mLifecycleCallbackExecutor = lifecycleCallbackExecutor;
+ }
+
+ /** @hide */
+ public void register(IContextHubService service) {
+ // TODO(b/378974199): Consider refactor these assertions
+ if (mServiceToken != null) {
+ // Already registered
+ return;
+ }
+ try {
+ IContextHubEndpoint serviceToken =
+ service.registerEndpoint(mPendingHubEndpointInfo, mServiceCallback);
+ mAssignedHubEndpointInfo = serviceToken.getAssignedHubEndpointInfo();
+ mServiceToken = serviceToken;
+ } catch (RemoteException e) {
+ Log.e(TAG, "registerEndpoint: failed to register endpoint", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void unregister() {
+ IContextHubEndpoint serviceToken = mServiceToken;
+ // TODO(b/378974199): Consider refactor these assertions
+ if (serviceToken == null) {
+ // Not yet registered
+ return;
+ }
+
+ try {
+ synchronized (mLock) {
+ // Don't call HubEndpointSession.close() here.
+ for (int i = 0; i < mActiveSessions.size(); i++) {
+ mActiveSessions.get(mActiveSessions.keyAt(i)).setClosed();
+ }
+ mActiveSessions.clear();
+ }
+ mServiceToken.unregister();
+ } catch (RemoteException e) {
+ Log.e(TAG, "unregisterEndpoint: failed to unregister endpoint", e);
+ e.rethrowFromSystemServer();
+ } finally {
+ mServiceToken = null;
+ mAssignedHubEndpointInfo = null;
+ }
+ }
+
+ /** @hide */
+ public void openSession(HubEndpointInfo destinationInfo) {
+ // TODO(b/378974199): Consider refactor these assertions
+ if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
+ // No longer registered?
+ return;
+ }
+
+ HubEndpointSession newSession;
+ try {
+ // Request system service to assign session id.
+ int sessionId = mServiceToken.openSession(destinationInfo);
+
+ // Save the newly created session
+ synchronized (mLock) {
+ newSession =
+ new HubEndpointSession(
+ sessionId,
+ HubEndpoint.this,
+ destinationInfo,
+ mAssignedHubEndpointInfo);
+ mActiveSessions.put(sessionId, newSession);
+ }
+ } catch (RemoteException e) {
+ // Move this to toString
+ Log.e(TAG, "openSession: failed to open session to " + destinationInfo, e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void closeSession(HubEndpointSession session) {
+ IContextHubEndpoint serviceToken = mServiceToken;
+ // TODO(b/378974199): Consider refactor these assertions
+ if (serviceToken == null || mAssignedHubEndpointInfo == null) {
+ // Not registered
+ return;
+ }
+
+ synchronized (mLock) {
+ if (!mActiveSessions.contains(session.getId())) {
+ // Already closed?
+ return;
+ }
+ session.setClosed();
+ mActiveSessions.remove(session.getId());
+ }
+
+ try {
+ // Oneway notification to system service
+ serviceToken.closeSession(
+ session.getId(),
+ IHubEndpointLifecycleCallback.REASON_CLOSE_ENDPOINT_SESSION_REQUESTED);
+ } catch (RemoteException e) {
+ Log.e(TAG, "closeSession: failed to close session " + session, e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @Nullable
+ public String getTag() {
+ return mPendingHubEndpointInfo.getTag();
+ }
+
+ @Nullable
+ public IHubEndpointLifecycleCallback getLifecycleCallback() {
+ return mLifecycleCallback;
+ }
+
+ /** Builder for a {@link HubEndpoint} object. */
+ public static final class Builder {
+ private final String mPackageName;
+
+ @Nullable private IHubEndpointLifecycleCallback mLifecycleCallback;
+
+ @NonNull private Executor mLifecycleCallbackExecutor;
+
+ @Nullable private String mTag;
+
+ /** Create a builder for {@link HubEndpoint} */
+ public Builder(@NonNull Context context) {
+ mPackageName = context.getPackageName();
+ mLifecycleCallbackExecutor = context.getMainExecutor();
+ }
+
+ /**
+ * Set a tag string. The tag can be used to further identify the creator of the endpoint.
+ * Endpoints created by the same package share the same name but should have different tags.
+ */
+ @NonNull
+ public Builder setTag(@NonNull String tag) {
+ mTag = tag;
+ return this;
+ }
+
+ /** Attach a callback interface for lifecycle events for this Endpoint */
+ @NonNull
+ public Builder setLifecycleCallback(
+ @NonNull IHubEndpointLifecycleCallback lifecycleCallback) {
+ mLifecycleCallback = lifecycleCallback;
+ return this;
+ }
+
+ /**
+ * Attach a callback interface for lifecycle events for this Endpoint with a specified
+ * executor
+ */
+ @NonNull
+ public Builder setLifecycleCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull IHubEndpointLifecycleCallback lifecycleCallback) {
+ mLifecycleCallbackExecutor = executor;
+ mLifecycleCallback = lifecycleCallback;
+ return this;
+ }
+
+ /** Build the {@link HubEndpoint} object. */
+ @NonNull
+ public HubEndpoint build() {
+ return new HubEndpoint(
+ new HubEndpointInfo(mPackageName, mTag),
+ mLifecycleCallback,
+ mLifecycleCallbackExecutor);
+ }
+ }
+}
diff --git a/core/java/android/hardware/contexthub/HubEndpointInfo.aidl b/core/java/android/hardware/contexthub/HubEndpointInfo.aidl
new file mode 100644
index 000000000000..025b2b1f685a
--- /dev/null
+++ b/core/java/android/hardware/contexthub/HubEndpointInfo.aidl
@@ -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 android.hardware.contexthub;
+
+/** @hide */
+parcelable HubEndpointInfo;
diff --git a/core/java/android/hardware/contexthub/HubEndpointInfo.java b/core/java/android/hardware/contexthub/HubEndpointInfo.java
new file mode 100644
index 000000000000..ed8ff2929aff
--- /dev/null
+++ b/core/java/android/hardware/contexthub/HubEndpointInfo.java
@@ -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 android.hardware.contexthub;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Parcelable representing an endpoint from ContextHub or VendorHub.
+ *
+ * <p>HubEndpointInfo contains information about an endpoint, including its name, tag and other
+ * information. A HubEndpointInfo object can be used to accurately identify a specific endpoint.
+ * Application can use this object to identify and describe an endpoint.
+ *
+ * <p>See: {@link android.hardware.location.ContextHubManager#findEndpoints} for how to retrieve
+ * {@link HubEndpointInfo} for endpoints on a hub.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public final class HubEndpointInfo implements Parcelable {
+ /**
+ * A unique identifier for one endpoint. A unique identifier for one endpoint consists of two
+ * parts: (1) a unique long number for a hub and (2) a long number for the endpoint, unique
+ * within a hub. This class overrides equality methods and can be used to compare if two
+ * endpoints are the same.
+ */
+ public static class HubEndpointIdentifier {
+ private final long mEndpointId;
+ private final long mHubId;
+
+ /** @hide */
+ public HubEndpointIdentifier(long hubId, long endpointId) {
+ mEndpointId = endpointId;
+ mHubId = hubId;
+ }
+
+ /** @hide */
+ public HubEndpointIdentifier(android.hardware.contexthub.EndpointId halEndpointId) {
+ mEndpointId = halEndpointId.id;
+ mHubId = halEndpointId.hubId;
+ }
+
+ /** Get the endpoint portion of the identifier. */
+ public long getEndpoint() {
+ return mEndpointId;
+ }
+
+ /** Get the hub portion of the identifier. */
+ public long getHub() {
+ return mHubId;
+ }
+
+ /**
+ * Create an invalid endpoint id, to represent endpoint that are not yet registered with the
+ * HAL.
+ *
+ * @hide
+ */
+ public static HubEndpointIdentifier invalid() {
+ return new HubEndpointIdentifier(
+ android.hardware.contexthub.HubInfo.HUB_ID_INVALID,
+ android.hardware.contexthub.EndpointId.ENDPOINT_ID_INVALID);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mEndpointId, mHubId);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof HubEndpointIdentifier other)) {
+ return false;
+ }
+ if (other.mHubId != mHubId) {
+ return false;
+ }
+ return other.mEndpointId == mEndpointId;
+ }
+ }
+
+ private final HubEndpointIdentifier mId;
+ private final String mName;
+ @Nullable private final String mTag;
+
+ // TODO(b/375487784): Add Service/version and other information to this object
+
+ /** @hide */
+ public HubEndpointInfo(android.hardware.contexthub.EndpointInfo endpointInfo) {
+ mId = new HubEndpointIdentifier(endpointInfo.id.hubId, endpointInfo.id.id);
+ mName = endpointInfo.name;
+ mTag = endpointInfo.tag;
+ }
+
+ /** @hide */
+ public HubEndpointInfo(String name, @Nullable String tag) {
+ mId = HubEndpointIdentifier.invalid();
+ mName = name;
+ mTag = tag;
+ }
+
+ private HubEndpointInfo(Parcel in) {
+ long hubId = in.readLong();
+ long endpointId = in.readLong();
+ mName = in.readString();
+ mTag = in.readString();
+
+ mId = new HubEndpointIdentifier(hubId, endpointId);
+ }
+
+ /** Parcel implementation details */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Parcel implementation details */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mId.getHub());
+ dest.writeLong(mId.getEndpoint());
+ dest.writeString(mName);
+ dest.writeString(mTag);
+ }
+
+ /** Get a unique identifier for this endpoint. */
+ @NonNull
+ public HubEndpointIdentifier getIdentifier() {
+ return mId;
+ }
+
+ /** Get the human-readable name of this endpoint (for debugging purposes). */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Get the tag that further identifies the submodule that created this endpoint. For example, a
+ * single application could provide multiple endpoints. These endpoints will share the same
+ * name, but will have different tags. This tag can be used to identify the submodule within the
+ * application that provided the endpoint.
+ */
+ @Nullable
+ public String getTag() {
+ return mTag;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder();
+ out.append("Endpoint [0x");
+ out.append(Long.toHexString(mId.getEndpoint()));
+ out.append("@ Hub 0x");
+ out.append(Long.toHexString(mId.getHub()));
+ out.append("] Name=");
+ out.append(mName);
+ out.append(", Tag=");
+ out.append(mTag);
+ return out.toString();
+ }
+
+ public static final @android.annotation.NonNull Creator<HubEndpointInfo> CREATOR =
+ new Creator<>() {
+ public HubEndpointInfo createFromParcel(Parcel in) {
+ return new HubEndpointInfo(in);
+ }
+
+ public HubEndpointInfo[] newArray(int size) {
+ return new HubEndpointInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java
new file mode 100644
index 000000000000..ef989f1f0d2d
--- /dev/null
+++ b/core/java/android/hardware/contexthub/HubEndpointSession.java
@@ -0,0 +1,115 @@
+/*
+ * 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.hardware.contexthub;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+import android.util.CloseGuard;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * An object representing a communication session between two different hub endpoints.
+ *
+ * <p>A published enpoint can receive
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public class HubEndpointSession implements AutoCloseable {
+ private final CloseGuard mCloseGuard = new CloseGuard();
+
+ private final int mId;
+
+ // TODO(b/377717509): Implement Message sending API & interface
+ @NonNull private final HubEndpoint mHubEndpoint;
+ @NonNull private final HubEndpointInfo mInitiator;
+ @NonNull private final HubEndpointInfo mDestination;
+
+ private final AtomicBoolean mIsClosed = new AtomicBoolean(true);
+
+ /** @hide */
+ HubEndpointSession(
+ int id,
+ @NonNull HubEndpoint hubEndpoint,
+ @NonNull HubEndpointInfo destination,
+ @NonNull HubEndpointInfo initiator) {
+ mId = id;
+ mHubEndpoint = hubEndpoint;
+ mDestination = destination;
+ mInitiator = initiator;
+ }
+
+ /** @hide */
+ public int getId() {
+ return mId;
+ }
+
+ /** @hide */
+ public void setOpened() {
+ mIsClosed.set(false);
+ mCloseGuard.open("close");
+ }
+
+ /** @hide */
+ public void setClosed() {
+ mIsClosed.set(true);
+ mCloseGuard.close();
+ }
+
+ /**
+ * Closes the connection for this session between an endpoint and the Context Hub Service.
+ *
+ * <p>When this function is invoked, the messaging associated with this session is invalidated.
+ * All futures messages targeted for this client are dropped.
+ */
+ public void close() {
+ if (!mIsClosed.getAndSet(true)) {
+ mCloseGuard.close();
+ mHubEndpoint.closeSession(this);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("Session [");
+ stringBuilder.append(mId);
+ stringBuilder.append("]: [");
+ stringBuilder.append(mInitiator);
+ stringBuilder.append("]->[");
+ stringBuilder.append(mDestination);
+ stringBuilder.append("]");
+ return stringBuilder.toString();
+ }
+
+ /** @hide */
+ protected void finalize() throws Throwable {
+ try {
+ // Note that guard could be null if the constructor threw.
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+}
diff --git a/core/java/android/hardware/contexthub/HubEndpointSessionResult.java b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java
new file mode 100644
index 000000000000..1f2bdb985008
--- /dev/null
+++ b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java
@@ -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 android.hardware.contexthub;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+
+/**
+ * Return type of {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}. The value determines
+ * whether a open session request from the remote is accepted or not.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public class HubEndpointSessionResult {
+ private final boolean mAccepted;
+
+ @Nullable private final String mReason;
+
+ private HubEndpointSessionResult(boolean accepted, @Nullable String reason) {
+ mAccepted = accepted;
+ mReason = reason;
+ }
+
+ /**
+ * Retrieve the decision of the session request.
+ *
+ * @return Whether a session request was accepted or not, previously set with {@link #accept()}
+ * or {@link #reject(String)}.
+ */
+ public boolean isAccepted() {
+ return mAccepted;
+ }
+
+ /**
+ * Retrieve the decision of the session request.
+ *
+ * @return The reason previously set in {@link #reject(String)}. If the result was {@link
+ * #accept()}, the reason will be null.
+ */
+ @Nullable
+ public String getReason() {
+ return mReason;
+ }
+
+ /** Accept the request. */
+ @NonNull
+ public static HubEndpointSessionResult accept() {
+ return new HubEndpointSessionResult(true, null);
+ }
+
+ /**
+ * Reject the request with a reason.
+ *
+ * @param reason Reason why the request was rejected, for diagnostic purposes.
+ */
+ @NonNull
+ public static HubEndpointSessionResult reject(@NonNull String reason) {
+ return new HubEndpointSessionResult(false, reason);
+ }
+}
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
new file mode 100644
index 000000000000..61e60e31760a
--- /dev/null
+++ b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.contexthub;
+
+import android.hardware.contexthub.HubEndpointInfo;
+
+/**
+ * @hide
+ */
+interface IContextHubEndpoint {
+ /**
+ * Retrieve the up-to-date EndpointInfo, with assigned endpoint id.
+ */
+ HubEndpointInfo getAssignedHubEndpointInfo();
+
+ /**
+ * Request system service to open a session with a specific destination.
+ *
+ * @param destination A valid HubEndpointInfo representing the destination.
+ *
+ * @throws IllegalArgumentException If the HubEndpointInfo is not valid.
+ * @throws IllegalStateException If there are too many opened sessions.
+ */
+ int openSession(in HubEndpointInfo destination);
+
+ /**
+ * Request system service to close a specific session
+ *
+ * @param sessionId An integer identifying the session, assigned by system service
+ * @param reason An integer identifying the reason
+ *
+ * @throws IllegalStateException If the session wasn't opened.
+ */
+ void closeSession(int sessionId, int reason);
+
+ /**
+ * Callback when a session is opened. This callback is the status callback for a previous
+ * IContextHubEndpointCallback.onSessionOpenRequest().
+ *
+ * @param sessionId The integer representing the communication session, previously set in
+ * onSessionOpenRequest().
+ *
+ * @throws IllegalStateException If the session wasn't opened.
+ */
+ void openSessionRequestComplete(int sessionId);
+
+ /**
+ * Unregister this endpoint from the HAL, invalidate the EndpointInfo previously assigned.
+ */
+ void unregister();
+}
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
new file mode 100644
index 000000000000..5656a4ac49da
--- /dev/null
+++ b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.contexthub;
+
+import android.hardware.contexthub.HubEndpointInfo;
+
+/**
+ * @hide
+ */
+oneway interface IContextHubEndpointCallback {
+ /**
+ * Request from system service to open a session, requested by a specific initiator.
+ *
+ * @param sessionId An integer identifying the session, assigned by the initiator
+ * @param initiator HubEndpointInfo representing the requester
+ */
+ void onSessionOpenRequest(int sessionId, in HubEndpointInfo initiator);
+
+ /**
+ * Request from system service to close a specific session
+ *
+ * @param sessionId An integer identifying the session
+ * @param reason An integer identifying the reason
+ */
+ void onSessionClosed(int sessionId, int reason);
+
+
+ /**
+ * Notifies the system service that the session requested by IContextHubEndpoint.openSession
+ * is ready to use.
+ *
+ * @param sessionId The integer representing the communication session, previously set in
+ * IContextHubEndpoint.openSession(). This id is assigned by the HAL.
+ */
+ void onSessionOpenComplete(int sessionId);
+}
diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
new file mode 100644
index 000000000000..5bd3c0ea23dc
--- /dev/null
+++ b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
@@ -0,0 +1,78 @@
+/*
+ * 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.hardware.contexthub;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Interface for listening to lifecycle events of a hub endpoint.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public interface IHubEndpointLifecycleCallback {
+ /** Unknown reason. */
+ int REASON_UNSPECIFIED = 0;
+
+ /** The peer rejected the request to open this endpoint session. */
+ int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3;
+
+ /** The peer closed this endpoint session. */
+ int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ REASON_UNSPECIFIED,
+ REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED,
+ REASON_CLOSE_ENDPOINT_SESSION_REQUESTED,
+ })
+ @interface EndpointLifecycleReason {}
+
+ /**
+ * Called when an endpoint is requesting a session be opened with another endpoint.
+ *
+ * @param requester The {@link HubEndpointInfo} object representing the requester
+ */
+ @NonNull
+ HubEndpointSessionResult onSessionOpenRequest(@NonNull HubEndpointInfo requester);
+
+ /**
+ * Called when a communication session is opened and ready to be used.
+ *
+ * @param session The {@link HubEndpointSession} object that can be used for communication
+ */
+ void onSessionOpened(@NonNull HubEndpointSession session);
+
+ /**
+ * Called when a communication session is requested to be closed, or the peer endpoint rejected
+ * the session open request.
+ *
+ * @param session The {@link HubEndpointSession} object that is now closed and shouldn't be
+ * used.
+ * @param reason The reason why this session was closed.
+ */
+ void onSessionClosed(@NonNull HubEndpointSession session, @EndpointLifecycleReason int reason);
+}
diff --git a/core/java/android/hardware/contexthub/OWNERS b/core/java/android/hardware/contexthub/OWNERS
new file mode 100644
index 000000000000..a65a2bf9ee36
--- /dev/null
+++ b/core/java/android/hardware/contexthub/OWNERS
@@ -0,0 +1,2 @@
+# ContextHub team
+file:platform/system/chre:/OWNERS
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index e6a1640781ed..25327a9b1d52 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -61,6 +61,7 @@ import android.view.Surface;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.display.feature.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -102,6 +103,7 @@ public final class DisplayManager {
private final WeakDisplayCache mDisplayCache = new WeakDisplayCache();
private int mDisplayIdToMirror = INVALID_DISPLAY;
+ private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
/**
* Broadcast receiver that indicates when the Wifi display status changes.
@@ -1613,6 +1615,17 @@ public final class DisplayManager {
}
/**
+ * Returns whether this device supports Always On Display.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_IS_ALWAYS_ON_AVAILABLE_API)
+ public boolean isAlwaysOnDisplayCurrentlyAvailable() {
+ return getAmbientDisplayConfiguration().alwaysOnAvailableForUser(mContext.getUserId());
+ }
+
+ /**
* Returns whether device supports seamless refresh rate switching.
*
* Match content frame rate setting has three options: seamless, non-seamless and never.
@@ -1674,6 +1687,15 @@ public final class DisplayManager {
}
}
+ private AmbientDisplayConfiguration getAmbientDisplayConfiguration() {
+ synchronized (this) {
+ if (mAmbientDisplayConfiguration == null) {
+ mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
+ }
+ }
+ return mAmbientDisplayConfiguration;
+ }
+
/**
* Creates a VirtualDisplay that will mirror the content of displayIdToMirror
* @param name The name for the virtual display
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index be710b1328e7..1e66beea42a6 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -17,6 +17,7 @@
package android.hardware.display;
+import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
import static android.hardware.display.DisplayManager.EventFlag;
import static android.Manifest.permission.MANAGE_DISPLAYS;
import static android.view.Display.HdrCapabilities.HdrType;
@@ -188,9 +189,11 @@ public final class DisplayManagerGlobal {
}
private PropertyInvalidatedCache<Integer, DisplayInfo> mDisplayCache =
- new PropertyInvalidatedCache<Integer, DisplayInfo>(
- 8, // size of display cache
- CACHE_KEY_DISPLAY_INFO_PROPERTY) {
+ new PropertyInvalidatedCache<>(
+ new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
+ .maxEntries(8).api(CACHE_KEY_DISPLAY_INFO_API).isolateUids(false),
+ CACHE_KEY_DISPLAY_INFO_API, null) {
+
@Override
public DisplayInfo recompute(Integer id) {
try {
@@ -1514,18 +1517,17 @@ public final class DisplayManagerGlobal {
}
/**
- * Name of the property containing a unique token which changes every time we update the
- * system's display configuration.
+ * The API portion of the key that identifies the unique PropertyInvalidatedCache token which
+ * changes every time we update the system's display configuration.
*/
- public static final String CACHE_KEY_DISPLAY_INFO_PROPERTY =
- PropertyInvalidatedCache.createSystemCacheKey("display_info");
+ private static final String CACHE_KEY_DISPLAY_INFO_API = "display_info";
/**
* Invalidates the contents of the display info cache for all applications. Can only
* be called by system_server.
*/
public static void invalidateLocalDisplayInfoCaches() {
- PropertyInvalidatedCache.invalidateCache(CACHE_KEY_DISPLAY_INFO_PROPERTY);
+ PropertyInvalidatedCache.invalidateCache(MODULE_SYSTEM, CACHE_KEY_DISPLAY_INFO_API);
}
/**
diff --git a/core/java/android/hardware/flags/overlayproperties_flags.aconfig b/core/java/android/hardware/flags/flags.aconfig
index 6c86108c4034..5ca6c6bed1f0 100644
--- a/core/java/android/hardware/flags/overlayproperties_flags.aconfig
+++ b/core/java/android/hardware/flags/flags.aconfig
@@ -2,6 +2,15 @@ package: "android.hardware.flags"
container: "system"
flag {
+ name: "luts_api"
+ is_exported: true
+ is_fixed_read_only: true
+ namespace: "core_graphics"
+ description: "public Luts related Apis"
+ bug: "349667978"
+}
+
+flag {
name: "overlayproperties_class_api"
is_exported: true
namespace: "core_graphics"
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 4b2f2c218e5a..fee074901c10 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -170,4 +170,11 @@ flag {
namespace: "input"
description: "Adds key gestures for talkback and magnifier"
bug: "375277034"
-} \ No newline at end of file
+}
+
+flag {
+ name: "can_window_override_power_gesture_api"
+ namespace: "wallet_integration"
+ description: "Adds new API in WindowManager class to check if the window can override the power key double tap behavior."
+ bug: "378736024"
+ } \ No newline at end of file
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 494bfc926384..b2c3bb89d863 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -34,6 +34,10 @@ import android.chre.flags.Flags;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.contexthub.ErrorCode;
+import android.hardware.contexthub.HubDiscoveryInfo;
+import android.hardware.contexthub.HubEndpoint;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IHubEndpointLifecycleCallback;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
@@ -42,6 +46,7 @@ import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -679,6 +684,29 @@ public final class ContextHubManager {
}
/**
+ * Find a list of endpoints that matches a specific ID.
+ *
+ * @param endpointId Statically generated ID for an endpoint.
+ * @return A list of {@link HubDiscoveryInfo} objects that represents the result of discovery.
+ */
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @NonNull
+ public List<HubDiscoveryInfo> findEndpoints(long endpointId) {
+ try {
+ List<HubEndpointInfo> endpointInfos = mService.findEndpoints(endpointId);
+ List<HubDiscoveryInfo> results = new ArrayList<>(endpointInfos.size());
+ // Wrap with result type
+ for (HubEndpointInfo endpointInfo : endpointInfos) {
+ results.add(new HubDiscoveryInfo(endpointInfo));
+ }
+ return results;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Set a callback to receive messages from the context hub
*
* @param callback Callback object
@@ -1010,6 +1038,55 @@ public final class ContextHubManager {
}
/**
+ * Registers an endpoint and its callback with the Context Hub Service.
+ *
+ * <p>An endpoint is registered with the Context Hub Service and published to the HAL. When the
+ * registration succeeds, the endpoint can receive notifications through the provided callback.
+ *
+ * @param hubEndpoint {@link HubEndpoint} object created by {@link HubEndpoint.Builder}
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+ public void registerEndpoint(@NonNull HubEndpoint hubEndpoint) {
+ hubEndpoint.register(mService);
+ }
+
+ /**
+ * Use a registered endpoint to connect to another endpoint (destination).
+ *
+ * <p>Context Hub Service will create the endpoint session and notify the registered endpoint.
+ * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback}
+ * object regarding the lifecycle events of the session
+ *
+ * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link
+ * ContextHubManager#registerEndpoint(HubEndpoint)}.
+ * @param destination {@link HubEndpointInfo} object that represents an endpoint from previous
+ * endpoint discovery results (e.g. from {@link ContextHubManager#findEndpoints(long)}).
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+ public void openSession(
+ @NonNull HubEndpoint hubEndpoint, @NonNull HubEndpointInfo destination) {
+ hubEndpoint.openSession(destination);
+ }
+
+ /**
+ * Unregisters an endpoint and its callback with the Context Hub Service.
+ *
+ * <p>An endpoint is unregistered from the HAL. The endpoint object will no longer receive
+ * notification through the provided callback.
+ *
+ * @param hubEndpoint {@link HubEndpoint} object created by {@link HubEndpoint.Builder}. This
+ * should match a previously registered object via {@link
+ * ContextHubManager#registerEndpoint(HubEndpoint)}.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+ public void unregisterEndpoint(@NonNull HubEndpoint hubEndpoint) {
+ hubEndpoint.unregister();
+ }
+
+ /**
* Queries for the list of preloaded nanoapp IDs on the system.
*
* @param hubInfo The Context Hub to query a list of nanoapp IDs from.
@@ -1168,6 +1245,7 @@ public final class ContextHubManager {
requireNonNull(mainLooper, "mainLooper cannot be null");
mService = service;
mMainLooper = mainLooper;
+
try {
mService.registerCallback(mClientCallback);
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index b0cc763dc8fd..512872303291 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -18,17 +18,20 @@ package android.hardware.location;
// Declare any non-default types here with import statements
import android.app.PendingIntent;
-import android.hardware.location.HubInfo;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IContextHubEndpoint;
+import android.hardware.contexthub.IContextHubEndpointCallback;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubMessage;
-import android.hardware.location.NanoApp;
-import android.hardware.location.NanoAppBinary;
-import android.hardware.location.NanoAppFilter;
-import android.hardware.location.NanoAppInstanceInfo;
+import android.hardware.location.HubInfo;
import android.hardware.location.IContextHubCallback;
import android.hardware.location.IContextHubClient;
import android.hardware.location.IContextHubClientCallback;
import android.hardware.location.IContextHubTransactionCallback;
+import android.hardware.location.NanoApp;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppFilter;
+import android.hardware.location.NanoAppInstanceInfo;
/**
* @hide
@@ -122,4 +125,12 @@ interface IContextHubService {
// Enables or disables test mode
@EnforcePermission("ACCESS_CONTEXT_HUB")
boolean setTestMode(in boolean enable);
+
+ // Finds all endpoints that havea specific ID
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
+ List<HubEndpointInfo> findEndpoints(long endpointId);
+
+ // Register an endpoint with the context hub
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
+ IContextHubEndpoint registerEndpoint(in HubEndpointInfo pendingEndpointInfo, in IContextHubEndpointCallback callback);
}
diff --git a/core/java/android/net/vcn/VcnFrameworkInitializer.java b/core/java/android/net/ConnectivityFrameworkInitializerBaklava.java
index 8cb213b306be..1f0fa92d7976 100644
--- a/core/java/android/net/vcn/VcnFrameworkInitializer.java
+++ b/core/java/android/net/ConnectivityFrameworkInitializerBaklava.java
@@ -14,15 +14,21 @@
* limitations under the License.
*/
-package android.net.vcn;
+package android.net;
+import static android.net.vcn.Flags.FLAG_MAINLINE_VCN_MODULE_API;
+
+import android.annotation.FlaggedApi;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.app.SystemServiceRegistry;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.net.vcn.IVcnManagementService;
+import android.net.vcn.VcnManager;
import android.os.Build;
import android.os.SystemProperties;
@@ -31,8 +37,9 @@ import android.os.SystemProperties;
*
* @hide
*/
-// TODO: Expose it as @SystemApi(client = MODULE_LIBRARIES)
-public final class VcnFrameworkInitializer {
+@FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class ConnectivityFrameworkInitializerBaklava {
/**
* Starting with {@link VANILLA_ICE_CREAM}, Telephony feature flags (e.g. {@link
* PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}) are being checked before returning managers
@@ -55,7 +62,7 @@ public final class VcnFrameworkInitializer {
*/
private static final int VENDOR_API_FOR_ANDROID_V = 202404;
- private VcnFrameworkInitializer() {}
+ private ConnectivityFrameworkInitializerBaklava() {}
// Suppressing AndroidFrameworkCompatChange because we're querying vendor
// partition SDK level, not application's target SDK version (which BTW we
@@ -86,7 +93,10 @@ public final class VcnFrameworkInitializer {
*
* @throws IllegalStateException if this is called anywhere besides {@link
* SystemServiceRegistry}.
+ * @hide
*/
+ @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static void registerServiceWrappers() {
SystemServiceRegistry.registerContextAwareService(
VcnManager.VCN_MANAGEMENT_SERVICE_STRING,
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index af93c964a8ba..3219ce81c256 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -16,6 +16,7 @@
package android.net.vcn;
import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
+import static android.net.vcn.Flags.FLAG_MAINLINE_VCN_MODULE_API;
import static android.net.vcn.Flags.FLAG_SAFE_MODE_CONFIG;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
@@ -82,7 +83,15 @@ import java.util.concurrent.TimeUnit;
* </ul>
*/
public final class VcnGatewayConnectionConfig {
- /** @hide */
+ /**
+ * Minimum NAT timeout not set.
+ *
+ * <p>When the timeout is not set, the device will automatically choose a keepalive interval and
+ * may reduce the keepalive frequency for power-optimization.
+ */
+ @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+ // This constant does not represent a minimum value. It indicates the value is not configured.
+ @SuppressLint("MinMaxConstant")
public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET = -1;
/** @hide */
@@ -773,7 +782,7 @@ public final class VcnGatewayConnectionConfig {
*
* @param minUdpPort4500NatTimeoutSeconds the maximum keepalive timeout supported by the VCN
* Gateway Connection, generally the minimum duration a NAT mapping is cached on the VCN
- * Gateway.
+ * Gateway; or {@link MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET} to clear this value.
* @return this {@link Builder} instance, for chaining
*/
@NonNull
@@ -781,8 +790,10 @@ public final class VcnGatewayConnectionConfig {
@IntRange(from = MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS)
int minUdpPort4500NatTimeoutSeconds) {
Preconditions.checkArgument(
- minUdpPort4500NatTimeoutSeconds >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
- "Timeout must be at least 120s");
+ minUdpPort4500NatTimeoutSeconds == MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET
+ || minUdpPort4500NatTimeoutSeconds
+ >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
+ "Timeout must be at least 120s or MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET");
mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds;
return this;
diff --git a/core/java/android/net/vcn/VcnTransportInfo.java b/core/java/android/net/vcn/VcnTransportInfo.java
index 1fc91eea3138..3638429f33fb 100644
--- a/core/java/android/net/vcn/VcnTransportInfo.java
+++ b/core/java/android/net/vcn/VcnTransportInfo.java
@@ -17,13 +17,16 @@
package android.net.vcn;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
+import static android.net.vcn.Flags.FLAG_MAINLINE_VCN_MODULE_API;
import static android.net.vcn.VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS;
import static android.net.vcn.VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import android.annotation.FlaggedApi;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.net.NetworkCapabilities;
import android.net.TransportInfo;
import android.net.wifi.WifiInfo;
@@ -52,23 +55,29 @@ import java.util.Objects;
* @hide
*/
// TODO: Do not store WifiInfo and subscription ID in VcnTransportInfo anymore
-public class VcnTransportInfo implements TransportInfo, Parcelable {
+@FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class VcnTransportInfo implements TransportInfo, Parcelable {
@Nullable private final WifiInfo mWifiInfo;
private final int mSubId;
private final int mMinUdpPort4500NatTimeoutSeconds;
+ /** @hide */
public VcnTransportInfo(@NonNull WifiInfo wifiInfo) {
this(wifiInfo, INVALID_SUBSCRIPTION_ID, MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET);
}
+ /** @hide */
public VcnTransportInfo(@NonNull WifiInfo wifiInfo, int minUdpPort4500NatTimeoutSeconds) {
this(wifiInfo, INVALID_SUBSCRIPTION_ID, minUdpPort4500NatTimeoutSeconds);
}
+ /** @hide */
public VcnTransportInfo(int subId) {
this(null /* wifiInfo */, subId, MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET);
}
+ /** @hide */
public VcnTransportInfo(int subId, int minUdpPort4500NatTimeoutSeconds) {
this(null /* wifiInfo */, subId, minUdpPort4500NatTimeoutSeconds);
}
@@ -86,6 +95,7 @@ public class VcnTransportInfo implements TransportInfo, Parcelable {
* <p>If the underlying Network for the associated VCN is Cellular, returns null.
*
* @return the WifiInfo if there is an underlying WiFi connection, else null.
+ * @hide
*/
@Nullable
public WifiInfo getWifiInfo() {
@@ -100,17 +110,27 @@ public class VcnTransportInfo implements TransportInfo, Parcelable {
*
* @return the Subscription ID if a cellular underlying Network is present, else {@link
* android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+ * @hide
*/
public int getSubId() {
return mSubId;
}
/**
- * Get the VCN provided UDP port 4500 NAT timeout
+ * Get the minimum duration that the VCN Gateway guarantees to preserve a NAT mapping.
*
- * @return the UDP 4500 NAT timeout, or
+ * <p>To ensure uninterrupted connectivity, the device must send keepalive packets before the
+ * timeout. Failure to do so may result in the mapping being cleared and connection termination.
+ * This value is used as a power-optimization hint for other IKEv2/IPsec use cases (e.g. VPNs,
+ * or IWLAN) to reduce the necessary keepalive frequency, thus conserving power and data.
+ *
+ * @return the minimum duration that the VCN Gateway guarantees to preserve a NAT mapping, or
* VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET if not set.
+ * @see VcnGatewayConnectionConfig.Builder#setMinUdpPort4500NatTimeoutSeconds(int)
+ * @hide
*/
+ @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public int getMinUdpPort4500NatTimeoutSeconds() {
return mMinUdpPort4500NatTimeoutSeconds;
}
@@ -129,12 +149,21 @@ public class VcnTransportInfo implements TransportInfo, Parcelable {
&& mMinUdpPort4500NatTimeoutSeconds == that.mMinUdpPort4500NatTimeoutSeconds;
}
- /** {@inheritDoc} */
+ /**
+ * {@inheritDoc}
+ *
+ * @hide
+ */
+ @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@Override
public int describeContents() {
return 0;
}
+ /** @hide */
+ @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@Override
@NonNull
public TransportInfo makeCopy(long redactions) {
@@ -149,6 +178,9 @@ public class VcnTransportInfo implements TransportInfo, Parcelable {
mMinUdpPort4500NatTimeoutSeconds);
}
+ /** @hide */
+ @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@Override
public long getApplicableRedactions() {
long redactions = REDACT_FOR_NETWORK_SETTINGS;
@@ -161,7 +193,13 @@ public class VcnTransportInfo implements TransportInfo, Parcelable {
return redactions;
}
- /** {@inheritDoc} */
+ /**
+ * {@inheritDoc}
+ *
+ * @hide
+ */
+ @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mSubId);
@@ -174,7 +212,13 @@ public class VcnTransportInfo implements TransportInfo, Parcelable {
return "VcnTransportInfo { mWifiInfo = " + mWifiInfo + ", mSubId = " + mSubId + " }";
}
- /** Implement the Parcelable interface */
+ /**
+ * Implement the Parcelable interface
+ *
+ * @hide
+ */
+ @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final @NonNull Creator<VcnTransportInfo> CREATOR =
new Creator<VcnTransportInfo>() {
public VcnTransportInfo createFromParcel(Parcel in) {
@@ -201,37 +245,63 @@ public class VcnTransportInfo implements TransportInfo, Parcelable {
}
};
- /** This class can be used to construct a {@link VcnTransportInfo}. */
+ /**
+ * This class can be used to construct a {@link VcnTransportInfo}.
+ *
+ * @hide
+ */
+ @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final class Builder {
private int mMinUdpPort4500NatTimeoutSeconds = MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET;
- /** Construct Builder */
+ /**
+ * Construct Builder
+ *
+ * @hide
+ */
+ @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public Builder() {}
/**
- * Sets the maximum supported IKEv2/IPsec NATT keepalive timeout.
+ * Set the minimum duration that the VCN Gateway guarantees to preserve a NAT mapping.
*
* <p>This is used as a power-optimization hint for other IKEv2/IPsec use cases (e.g. VPNs,
* or IWLAN) to reduce the necessary keepalive frequency, thus conserving power and data.
*
- * @param minUdpPort4500NatTimeoutSeconds the maximum keepalive timeout supported by the VCN
- * Gateway Connection, generally the minimum duration a NAT mapping is cached on the VCN
- * Gateway.
+ * @param minUdpPort4500NatTimeoutSeconds the minimum duration that the VCN Gateway
+ * guarantees to preserve a NAT mapping, or {@link MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET}
+ * to clear this value. To ensure uninterrupted connectivity, the device must send
+ * keepalive packets within this interval. Failure to do so may result in the mapping
+ * being cleared and connection termination.
* @return this {@link Builder} instance, for chaining
+ * @see VcnGatewayConnectionConfig.Builder#setMinUdpPort4500NatTimeoutSeconds(int)
+ * @hide
*/
+ @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@NonNull
public Builder setMinUdpPort4500NatTimeoutSeconds(
@IntRange(from = MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS)
int minUdpPort4500NatTimeoutSeconds) {
Preconditions.checkArgument(
- minUdpPort4500NatTimeoutSeconds >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
- "Timeout must be at least 120s");
+ minUdpPort4500NatTimeoutSeconds == MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET
+ || minUdpPort4500NatTimeoutSeconds
+ >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
+ "Timeout must be at least 120s or MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET");
mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds;
return Builder.this;
}
- /** Build a VcnTransportInfo instance */
+ /**
+ * Build a VcnTransportInfo instance
+ *
+ * @hide
+ */
+ @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@NonNull
public VcnTransportInfo build() {
return new VcnTransportInfo(
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 99e7d166446e..05bd10b053fe 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -18,10 +18,12 @@ package android.os;
import static java.util.Objects.requireNonNull;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.util.ArrayMap;
import android.util.Size;
@@ -72,16 +74,18 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
/**
* Status when the Bundle can <b>assert</b> that the underlying Parcel DOES NOT contain
* Binder object(s).
- *
* @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_HAS_BINDERS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int STATUS_BINDERS_NOT_PRESENT = 0;
/**
* Status when the Bundle can <b>assert</b> that there are Binder object(s) in the Parcel.
- *
* @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_HAS_BINDERS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int STATUS_BINDERS_PRESENT = 1;
/**
@@ -94,9 +98,10 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* object to the Bundle but it is not possible to assert this fact unless the Bundle is written
* to a Parcel.
* </p>
- *
* @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_HAS_BINDERS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int STATUS_BINDERS_UNKNOWN = 2;
/** @hide */
@@ -417,6 +422,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* Returns a status indicating whether the bundle contains any parcelled Binder objects.
* @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_HAS_BINDERS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public @HasBinderStatus int hasBinders() {
if ((mFlags & FLAG_HAS_BINDERS_KNOWN) != 0) {
if ((mFlags & FLAG_HAS_BINDERS) != 0) {
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 7529ab9ab894..036ccd84a600 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -19,6 +19,8 @@ package android.os;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.app.Instrumentation;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Process;
import android.os.UserHandle;
@@ -31,7 +33,6 @@ import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import dalvik.annotation.optimization.NeverCompile;
-import dalvik.system.VMDebug;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
@@ -111,11 +112,39 @@ public final class MessageQueue {
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
MessageQueue(boolean quitAllowed) {
- mUseConcurrent = UserHandle.isCore(Process.myUid()) && !VMDebug.isDebuggingEnabled();
+ // Concurrent mode modifies behavior that is observable via reflection and is commonly used
+ // by tests.
+ // For now, we limit it to system processes to avoid breaking apps and their tests.
+ mUseConcurrent = UserHandle.isCore(Process.myUid());
+ // Even then, we don't use it if instrumentation is loaded as it breaks some
+ // platform tests.
+ final Instrumentation instrumentation = getInstrumentation();
+ mUseConcurrent &= instrumentation == null || !instrumentation.isInstrumenting();
+ // We can lift this restriction in the future after we've made it possible for test authors
+ // to test Looper and MessageQueue without resorting to reflection.
+
+ // Holdback study.
+ if (mUseConcurrent && Flags.messageQueueForceLegacy()) {
+ mUseConcurrent = false;
+ }
+
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
+ @android.ravenwood.annotation.RavenwoodReplace(blockedBy = ActivityThread.class)
+ private static Instrumentation getInstrumentation() {
+ final ActivityThread activityThread = ActivityThread.currentActivityThread();
+ if (activityThread != null) {
+ return activityThread.getInstrumentation();
+ }
+ return null;
+ }
+
+ private static Instrumentation getInstrumentation$ravenwood() {
+ return null; // Instrumentation not supported on Ravenwood yet.
+ }
+
@Override
protected void finalize() throws Throwable {
try {
diff --git a/core/java/android/os/CpuHeadroomParams.java b/core/java/android/os/CpuHeadroomParams.java
new file mode 100644
index 000000000000..f0d4f7d8737f
--- /dev/null
+++ b/core/java/android/os/CpuHeadroomParams.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.os.health.SystemHealthManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Headroom request params used by {@link SystemHealthManager#getCpuHeadroom(CpuHeadroomParams)}.
+ */
+@FlaggedApi(Flags.FLAG_CPU_GPU_HEADROOMS)
+public final class CpuHeadroomParams {
+ final CpuHeadroomParamsInternal mInternal;
+
+ public CpuHeadroomParams() {
+ mInternal = new CpuHeadroomParamsInternal();
+ }
+
+ /** @hide */
+ @IntDef(flag = false, prefix = {"CPU_HEADROOM_CALCULATION_TYPE_"}, value = {
+ CPU_HEADROOM_CALCULATION_TYPE_MIN, // 0
+ CPU_HEADROOM_CALCULATION_TYPE_AVERAGE, // 1
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CpuHeadroomCalculationType {
+ }
+
+ /**
+ * Calculates the headroom based on minimum value over a device-defined window.
+ */
+ public static final int CPU_HEADROOM_CALCULATION_TYPE_MIN = 0;
+
+ /**
+ * Calculates the headroom based on average value over a device-defined window.
+ */
+ public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1;
+
+ /**
+ * Sets the headroom calculation type.
+ * <p>
+ *
+ * @throws IllegalArgumentException if the type is invalid.
+ */
+ public void setCalculationType(@CpuHeadroomCalculationType int calculationType) {
+ switch (calculationType) {
+ case CPU_HEADROOM_CALCULATION_TYPE_MIN:
+ case CPU_HEADROOM_CALCULATION_TYPE_AVERAGE:
+ mInternal.calculationType = (byte) calculationType;
+ return;
+ }
+ throw new IllegalArgumentException("Invalid calculation type: " + calculationType);
+ }
+
+ /**
+ * Gets the headroom calculation type.
+ * Default to {@link #CPU_HEADROOM_CALCULATION_TYPE_MIN} if not set.
+ */
+ public @CpuHeadroomCalculationType int getCalculationType() {
+ @CpuHeadroomCalculationType int validatedType = switch ((int) mInternal.calculationType) {
+ case CPU_HEADROOM_CALCULATION_TYPE_MIN, CPU_HEADROOM_CALCULATION_TYPE_AVERAGE ->
+ mInternal.calculationType;
+ default -> CPU_HEADROOM_CALCULATION_TYPE_MIN;
+ };
+ return validatedType;
+ }
+
+ /**
+ * @hide
+ */
+ public CpuHeadroomParamsInternal getInternal() {
+ return mInternal;
+ }
+}
diff --git a/core/java/android/os/CpuHeadroomParamsInternal.aidl b/core/java/android/os/CpuHeadroomParamsInternal.aidl
new file mode 100644
index 000000000000..6cc4699a809e
--- /dev/null
+++ b/core/java/android/os/CpuHeadroomParamsInternal.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.hardware.power.CpuHeadroomParams;
+
+/**
+ * Changes should be synced with match function of HintManagerService#CpuHeadroomCacheItem.
+ * {@hide}
+ */
+@JavaDerive(equals = true, toString = true)
+parcelable CpuHeadroomParamsInternal {
+ boolean usesDeviceHeadroom = false;
+ CpuHeadroomParams.CalculationType calculationType = CpuHeadroomParams.CalculationType.MIN;
+ CpuHeadroomParams.SelectionType selectionType = CpuHeadroomParams.SelectionType.ALL;
+}
+
diff --git a/core/java/android/os/GpuHeadroomParams.java b/core/java/android/os/GpuHeadroomParams.java
new file mode 100644
index 000000000000..efb2a28ad2b5
--- /dev/null
+++ b/core/java/android/os/GpuHeadroomParams.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.os.health.SystemHealthManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Headroom request params used by {@link SystemHealthManager#getGpuHeadroom(GpuHeadroomParams)}.
+ */
+@FlaggedApi(Flags.FLAG_CPU_GPU_HEADROOMS)
+public final class GpuHeadroomParams {
+ final GpuHeadroomParamsInternal mInternal;
+
+ public GpuHeadroomParams() {
+ mInternal = new GpuHeadroomParamsInternal();
+ }
+
+ /** @hide */
+ @IntDef(flag = false, prefix = {"GPU_HEADROOM_CALCULATION_TYPE_"}, value = {
+ GPU_HEADROOM_CALCULATION_TYPE_MIN, // 0
+ GPU_HEADROOM_CALCULATION_TYPE_AVERAGE, // 1
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface GpuHeadroomCalculationType {
+ }
+
+ /**
+ * Calculates the headroom based on minimum value over a device-defined window.
+ */
+ public static final int GPU_HEADROOM_CALCULATION_TYPE_MIN = 0;
+
+ /**
+ * Calculates the headroom based on average value over a device-defined window.
+ */
+ public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1;
+
+ /**
+ * Sets the headroom calculation type.
+ * <p>
+ *
+ * @throws IllegalArgumentException if the type is invalid.
+ */
+ public void setCalculationType(@GpuHeadroomCalculationType int calculationType) {
+ switch (calculationType) {
+ case GPU_HEADROOM_CALCULATION_TYPE_MIN:
+ case GPU_HEADROOM_CALCULATION_TYPE_AVERAGE:
+ mInternal.calculationType = (byte) calculationType;
+ return;
+ }
+ throw new IllegalArgumentException("Invalid calculation type: " + calculationType);
+ }
+
+ /**
+ * Gets the headroom calculation type.
+ * Default to {@link #GPU_HEADROOM_CALCULATION_TYPE_MIN} if not set.
+ */
+ public @GpuHeadroomCalculationType int getCalculationType() {
+ @GpuHeadroomCalculationType int validatedType = switch ((int) mInternal.calculationType) {
+ case GPU_HEADROOM_CALCULATION_TYPE_MIN, GPU_HEADROOM_CALCULATION_TYPE_AVERAGE ->
+ mInternal.calculationType;
+ default -> GPU_HEADROOM_CALCULATION_TYPE_MIN;
+ };
+ return validatedType;
+ }
+
+ /**
+ * @hide
+ */
+ public GpuHeadroomParamsInternal getInternal() {
+ return mInternal;
+ }
+}
diff --git a/core/java/android/os/GpuHeadroomParamsInternal.aidl b/core/java/android/os/GpuHeadroomParamsInternal.aidl
new file mode 100644
index 000000000000..20309e7673f2
--- /dev/null
+++ b/core/java/android/os/GpuHeadroomParamsInternal.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.hardware.power.GpuHeadroomParams;
+
+/**
+ * Changes should be synced with match function of HintManagerService#GpuHeadroomCacheItem.
+ * {@hide}
+ */
+@JavaDerive(equals = true, toString = true)
+parcelable GpuHeadroomParamsInternal {
+ GpuHeadroomParams.CalculationType calculationType = GpuHeadroomParams.CalculationType.MIN;
+}
diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl
index 73cdd5682f31..33120556339f 100644
--- a/core/java/android/os/IHintManager.aidl
+++ b/core/java/android/os/IHintManager.aidl
@@ -17,6 +17,8 @@
package android.os;
+import android.os.CpuHeadroomParamsInternal;
+import android.os.GpuHeadroomParamsInternal;
import android.os.IHintSession;
import android.hardware.power.ChannelConfig;
import android.hardware.power.SessionConfig;
@@ -50,4 +52,8 @@ interface IHintManager {
*/
@nullable ChannelConfig getSessionChannel(in IBinder token);
oneway void closeSessionChannel();
+ float[] getCpuHeadroom(in CpuHeadroomParamsInternal params);
+ long getCpuHeadroomMinIntervalMillis();
+ float getGpuHeadroom(in GpuHeadroomParamsInternal params);
+ long getGpuHeadroomMinIntervalMillis();
}
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 590ddb404b63..e63b6648a9ef 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -78,6 +78,9 @@ per-file PatternMatcher* = file:/PACKAGE_MANAGER_OWNERS
# PermissionEnforcer
per-file PermissionEnforcer.java = tweek@google.com, brufino@google.com
+# RemoteCallbackList
+per-file RemoteCallbackList.java = shayba@google.com
+
# ART
per-file ArtModuleServiceManager.java = file:platform/art:/OWNERS
@@ -125,3 +128,6 @@ per-file StatsServiceManager.java = file:/services/core/java/com/android/server/
# Dropbox
per-file DropBoxManager* = mwachens@google.com
+
+# Flags
+per-file flags.aconfig = file:/FF_LEADS_OWNERS
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 9ab92285c167..5a53bc1552b8 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5308,7 +5308,13 @@ public class UserManager {
Manifest.permission.MANAGE_USERS,
Manifest.permission.CREATE_USERS,
Manifest.permission.QUERY_USERS}, conditional = true)
+ @CachedProperty(api = "user_manager_user_data")
public List<UserInfo> getProfiles(@UserIdInt int userId) {
+ if (android.multiuser.Flags.cacheProfilesReadOnly()) {
+ return UserManagerCache.getProfiles(
+ (Integer userIdentifier) -> mService.getProfiles(userIdentifier, false),
+ userId);
+ }
try {
return mService.getProfiles(userId, false /* enabledOnly */);
} catch (RemoteException re) {
@@ -6484,6 +6490,19 @@ public class UserManager {
}
/**
+ * This method is used to invalidate caches, when UserManagerService.mUsers
+ * {@link UserManagerService.UserData} is modified, including changes to {@link UserInfo}.
+ * In practice we determine modification by when that data is persisted, or scheduled to be
+ * presisted, to xml.
+ * @hide
+ */
+ public static final void invalidateCacheOnUserDataChanged() {
+ if (android.multiuser.Flags.cacheProfilesReadOnly()) {
+ UserManagerCache.invalidateProfiles();
+ }
+ }
+
+ /**
* Returns a serial number on this device for a given userId. User handles can be recycled
* when deleting and creating users, but serial numbers are not reused until the device is
* wiped.
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index d9db28e0b3c3..01445069af67 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -4,6 +4,15 @@ container: "system"
# keep-sorted start block=yes newline_separated=yes
flag {
+ # Holdback study for concurrent MessageQueue.
+ # Do not promote beyond trunkfood.
+ namespace: "system_performance"
+ name: "message_queue_force_legacy"
+ description: "Whether to holdback concurrent MessageQueue (force legacy)."
+ bug: "336880969"
+}
+
+flag {
name: "adpf_gpu_report_actual_work_duration"
is_exported: true
namespace: "game"
@@ -66,6 +75,14 @@ flag {
}
flag {
+ name: "adpf_use_load_hints"
+ namespace: "game"
+ description: "Guards use of the ADPF public load hints behind a readonly flag"
+ is_fixed_read_only: true
+ bug: "367803904"
+}
+
+flag {
name: "allow_consentless_bugreport_delegated_consent"
namespace: "crumpet"
description: "Allow privileged apps to call bugreport generation without enforcing user consent and delegate it to the calling app instead"
@@ -148,6 +165,13 @@ flag {
}
flag {
+ name: "cpu_gpu_headrooms"
+ namespace: "game"
+ description: "Feature flag for adding CPU/GPU headroom API"
+ bug: "346604998"
+}
+
+flag {
name: "disallow_cellular_null_ciphers_restriction"
namespace: "cellular_security"
description: "Guards a new UserManager user restriction that admins can use to require cellular encryption on their managed devices."
@@ -253,6 +277,15 @@ flag {
flag {
namespace: "system_performance"
+ name: "enable_has_binders"
+ is_exported: true
+ description: "Add hasBinders to Public API under a flag."
+ is_fixed_read_only: true
+ bug: "330345513"
+}
+
+flag {
+ namespace: "system_performance"
name: "perfetto_sdk_tracing"
description: "Tracing using Perfetto SDK."
bug: "303199244"
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index deabfed365a6..4db9bc333e2b 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -17,6 +17,7 @@
package android.os.health;
import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
@@ -25,6 +26,11 @@ import android.content.Context;
import android.os.BatteryStats;
import android.os.Build;
import android.os.Bundle;
+import android.os.CpuHeadroomParams;
+import android.os.CpuHeadroomParamsInternal;
+import android.os.GpuHeadroomParams;
+import android.os.GpuHeadroomParamsInternal;
+import android.os.IHintManager;
import android.os.IPowerStatsService;
import android.os.OutcomeReceiver;
import android.os.PowerMonitor;
@@ -68,6 +74,8 @@ public class SystemHealthManager {
private final IBatteryStats mBatteryStats;
@Nullable
private final IPowerStatsService mPowerStats;
+ @Nullable
+ private final IHintManager mHintManager;
private List<PowerMonitor> mPowerMonitorsInfo;
private final Object mPowerMonitorsLock = new Object();
private static final long TAKE_UID_SNAPSHOT_TIMEOUT_MILLIS = 10_000;
@@ -88,14 +96,111 @@ public class SystemHealthManager {
public SystemHealthManager() {
this(IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME)),
IPowerStatsService.Stub.asInterface(
- ServiceManager.getService(Context.POWER_STATS_SERVICE)));
+ ServiceManager.getService(Context.POWER_STATS_SERVICE)),
+ IHintManager.Stub.asInterface(
+ ServiceManager.getService(Context.PERFORMANCE_HINT_SERVICE)));
}
/** {@hide} */
public SystemHealthManager(@NonNull IBatteryStats batteryStats,
- @Nullable IPowerStatsService powerStats) {
+ @Nullable IPowerStatsService powerStats, @Nullable IHintManager hintManager) {
mBatteryStats = batteryStats;
mPowerStats = powerStats;
+ mHintManager = hintManager;
+ }
+
+ /**
+ * Provides an estimate of global available CPU headroom of the calling thread.
+ * <p>
+ *
+ * @param params params to customize the CPU headroom calculation, null to use default params.
+ * @return a single value a {@code Float.NaN} if it's temporarily unavailable.
+ * A valid value is ranged from [0, 100], where 0 indicates no more CPU resources can be
+ * granted.
+ * @throws UnsupportedOperationException if the API is unsupported or the request params can't
+ * be served.
+ */
+ @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS)
+ public @FloatRange(from = 0f, to = 100f) float getCpuHeadroom(
+ @Nullable CpuHeadroomParams params) {
+ if (mHintManager == null) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return mHintManager.getCpuHeadroom(
+ params != null ? params.getInternal() : new CpuHeadroomParamsInternal())[0];
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+
+
+ /**
+ * Provides an estimate of global available GPU headroom of the device.
+ * <p>
+ *
+ * @param params params to customize the GPU headroom calculation, null to use default params.
+ * @return a single value headroom or a {@code Float.NaN} if it's temporarily unavailable.
+ * A valid value is ranged from [0, 100], where 0 indicates no more GPU resources can be
+ * granted.
+ * @throws UnsupportedOperationException if the API is unsupported or the request params can't
+ * be served.
+ */
+ @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS)
+ public @FloatRange(from = 0f, to = 100f) float getGpuHeadroom(
+ @Nullable GpuHeadroomParams params) {
+ if (mHintManager == null) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return mHintManager.getGpuHeadroom(
+ params != null ? params.getInternal() : new GpuHeadroomParamsInternal());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Minimum polling interval for calling {@link #getCpuHeadroom(CpuHeadroomParams)} in
+ * milliseconds.
+ * <p>
+ * The {@link #getCpuHeadroom(CpuHeadroomParams)} API may return cached result if called more
+ * frequent than the interval.
+ *
+ * @throws UnsupportedOperationException if the API is unsupported.
+ */
+ @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS)
+ public long getCpuHeadroomMinIntervalMillis() {
+ if (mHintManager == null) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return mHintManager.getCpuHeadroomMinIntervalMillis();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Minimum polling interval for calling {@link #getGpuHeadroom(GpuHeadroomParams)} in
+ * milliseconds.
+ * <p>
+ * The {@link #getGpuHeadroom(GpuHeadroomParams)} API may return cached result if called more
+ * frequent than the interval.
+ *
+ * @throws UnsupportedOperationException if the API is unsupported.
+ */
+ @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS)
+ public long getGpuHeadroomMinIntervalMillis() {
+ if (mHintManager == null) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return mHintManager.getGpuHeadroomMinIntervalMillis();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
}
/**
@@ -261,7 +366,7 @@ public class SystemHealthManager {
mPowerMonitorsInfo = result;
}
if (executor != null) {
- executor.execute(()-> onResult.accept(result));
+ executor.execute(() -> onResult.accept(result));
} else {
onResult.accept(result);
}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 6264fbbbcb7a..0a35fe399531 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -399,4 +399,13 @@ flag {
namespace: "supervision"
description: "This flag is used to enable all the remaining permissions required to the supervision role"
bug: "367333883"
-} \ No newline at end of file
+}
+
+flag {
+ name: "permission_request_short_circuit_enabled"
+ is_fixed_read_only: true
+ is_exported: true
+ namespace: "permissions"
+ description: "This flag is used to short circuit the request for permananently denied permissions"
+ bug: "378923900"
+}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index ce901217d700..09004b3dcf03 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -115,6 +115,14 @@ flag {
}
flag {
+ name: "protect_device_config_flags"
+ namespace: "psap_ai"
+ description: "Feature flag to limit adb shell to allowlisted flags"
+ bug: "364083026"
+ is_fixed_read_only: true
+}
+
+flag {
name: "keystore_grant_api"
namespace: "hardware_backed_security"
description: "Feature flag for exposing KeyStore grant APIs"
diff --git a/core/java/android/security/forensic/ForensicManager.java b/core/java/android/security/forensic/ForensicManager.java
new file mode 100644
index 000000000000..9126182eda7b
--- /dev/null
+++ b/core/java/android/security/forensic/ForensicManager.java
@@ -0,0 +1,276 @@
+/*
+ * 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.security.forensic;
+
+import static android.Manifest.permission.MANAGE_FORENSIC_STATE;
+import static android.Manifest.permission.READ_FORENSIC_STATE;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.security.Flags;
+import android.util.Log;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * ForensicManager manages the forensic logging on Android devices.
+ * Upon user consent, forensic logging collects various device events for
+ * off-device investigation of potential device compromise.
+ * <p>
+ * Forensic logging can either be enabled ({@link #STATE_ENABLED}
+ * or disabled ({@link #STATE_DISABLED}).
+ * <p>
+ * The Forensic logs will be transferred to
+ * {@link android.security.forensic.ForensicEventTransport}.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_AFL_API)
+@SystemService(Context.FORENSIC_SERVICE)
+public class ForensicManager {
+ private static final String TAG = "ForensicManager";
+
+ /** @hide */
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "STATE_" }, value = {
+ STATE_UNKNOWN,
+ STATE_DISABLED,
+ STATE_ENABLED
+ })
+ public @interface ForensicState {}
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_UNKNOWN,
+ ERROR_PERMISSION_DENIED,
+ ERROR_TRANSPORT_UNAVAILABLE,
+ ERROR_DATA_SOURCE_UNAVAILABLE
+ })
+ public @interface ForensicError {}
+
+ /**
+ * Indicates an unknown state
+ */
+ public static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN;
+
+ /**
+ * Indicates an state that the forensic is turned off.
+ */
+ public static final int STATE_DISABLED = IForensicServiceStateCallback.State.DISABLED;
+
+ /**
+ * Indicates an state that the forensic is turned on.
+ */
+ public static final int STATE_ENABLED = IForensicServiceStateCallback.State.ENABLED;
+
+ /**
+ * Indicates an unknown error
+ */
+ public static final int ERROR_UNKNOWN = IForensicServiceCommandCallback.ErrorCode.UNKNOWN;
+
+ /**
+ * Indicates an error due to insufficient access rights.
+ */
+ public static final int ERROR_PERMISSION_DENIED =
+ IForensicServiceCommandCallback.ErrorCode.PERMISSION_DENIED;
+
+ /**
+ * Indicates an error due to unavailability of the forensic event transport.
+ */
+ public static final int ERROR_TRANSPORT_UNAVAILABLE =
+ IForensicServiceCommandCallback.ErrorCode.TRANSPORT_UNAVAILABLE;
+
+ /**
+ * Indicates an error due to unavailability of the data source.
+ */
+ public static final int ERROR_DATA_SOURCE_UNAVAILABLE =
+ IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE;
+
+
+ private final IForensicService mService;
+
+ private final ConcurrentHashMap<Consumer<Integer>, IForensicServiceStateCallback>
+ mStateCallbacks = new ConcurrentHashMap<>();
+
+ /**
+ * Constructor
+ *
+ * @param service A valid instance of IForensicService.
+ * @hide
+ */
+ public ForensicManager(IForensicService service) {
+ mService = service;
+ }
+
+ /**
+ * Add a callback to monitor the state of the ForensicService.
+ *
+ * @param executor The executor through which the callback should be invoked.
+ * @param callback The callback for state change.
+ * Once the callback is registered, the callback will be called
+ * to reflect the init state.
+ * The callback can be registered only once.
+ */
+ @RequiresPermission(READ_FORENSIC_STATE)
+ public void addStateCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull @ForensicState Consumer<Integer> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ if (mStateCallbacks.get(callback) != null) {
+ Log.d(TAG, "addStateCallback callback already present");
+ return;
+ }
+
+ final IForensicServiceStateCallback wrappedCallback =
+ new IForensicServiceStateCallback.Stub() {
+ @Override
+ public void onStateChange(int state) {
+ executor.execute(() -> callback.accept(state));
+ }
+ };
+ try {
+ mService.addStateCallback(wrappedCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ mStateCallbacks.put(callback, wrappedCallback);
+ }
+
+ /**
+ * Remove a callback to monitor the state of the ForensicService.
+ *
+ * @param callback The callback to remove.
+ */
+ @RequiresPermission(READ_FORENSIC_STATE)
+ public void removeStateCallback(@NonNull Consumer<@ForensicState Integer> callback) {
+ Objects.requireNonNull(callback);
+ if (!mStateCallbacks.containsKey(callback)) {
+ Log.d(TAG, "removeStateCallback callback not present");
+ return;
+ }
+
+ IForensicServiceStateCallback wrappedCallback = mStateCallbacks.get(callback);
+
+ try {
+ mService.removeStateCallback(wrappedCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ mStateCallbacks.remove(callback);
+ }
+
+ /**
+ * Enable forensic logging.
+ * If successful, ForensicService will transition to {@link #STATE_ENABLED} state.
+ * <p>
+ * When forensic logging is enabled, various device events will be collected and
+ * sent over to the registered {@link android.security.forensic.ForensicEventTransport}.
+ *
+ * @param executor The executor through which the callback should be invoked.
+ * @param callback The callback for the command result.
+ */
+ @RequiresPermission(MANAGE_FORENSIC_STATE)
+ public void enable(@NonNull @CallbackExecutor Executor executor,
+ @NonNull CommandCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ try {
+ mService.enable(new IForensicServiceCommandCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(callback::onSuccess);
+ }
+
+ @Override
+ public void onFailure(int error) {
+ executor.execute(() -> callback.onFailure(error));
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Disable forensic logging.
+ * If successful, ForensicService will transition to {@link #STATE_DISABLED}.
+ * <p>
+ * When forensic logging is disabled, device events will no longer be collected.
+ * Any events that have been collected but not yet sent to ForensicEventTransport
+ * will be transferred as a final batch.
+ *
+ * @param executor The executor through which the callback should be invoked.
+ * @param callback The callback for the command result.
+ */
+ @RequiresPermission(MANAGE_FORENSIC_STATE)
+ public void disable(@NonNull @CallbackExecutor Executor executor,
+ @NonNull CommandCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ try {
+ mService.disable(new IForensicServiceCommandCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ executor.execute(callback::onSuccess);
+ }
+
+ @Override
+ public void onFailure(int error) {
+ executor.execute(() -> callback.onFailure(error));
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Callback used in {@link #enable} and {@link #disable} to indicate the result of the command.
+ */
+ public interface CommandCallback {
+ /**
+ * Called when command succeeds.
+ */
+ void onSuccess();
+
+ /**
+ * Called when command fails.
+ * @param error The error number.
+ */
+ void onFailure(@ForensicError int error);
+ }
+}
diff --git a/core/java/android/security/forensic/IBackupTransport.aidl b/core/java/android/security/forensic/IForensicEventTransport.aidl
index c2cbc83ba1b3..80e78eb9cf49 100644
--- a/core/java/android/security/forensic/IBackupTransport.aidl
+++ b/core/java/android/security/forensic/IForensicEventTransport.aidl
@@ -20,7 +20,7 @@ import android.security.forensic.ForensicEvent;
import com.android.internal.infra.AndroidFuture;
/** {@hide} */
-oneway interface IBackupTransport {
+oneway interface IForensicEventTransport {
/**
* Initialize the server side.
*/
diff --git a/core/java/android/security/forensic/IForensicService.aidl b/core/java/android/security/forensic/IForensicService.aidl
index a944b18cb26d..8039b264f0e5 100644
--- a/core/java/android/security/forensic/IForensicService.aidl
+++ b/core/java/android/security/forensic/IForensicService.aidl
@@ -24,9 +24,12 @@ import android.security.forensic.IForensicServiceStateCallback;
* @hide
*/
interface IForensicService {
- void monitorState(IForensicServiceStateCallback callback);
- void makeVisible(IForensicServiceCommandCallback callback);
- void makeInvisible(IForensicServiceCommandCallback callback);
+ @EnforcePermission("READ_FORENSIC_STATE")
+ void addStateCallback(IForensicServiceStateCallback callback);
+ @EnforcePermission("READ_FORENSIC_STATE")
+ void removeStateCallback(IForensicServiceStateCallback callback);
+ @EnforcePermission("MANAGE_FORENSIC_STATE")
void enable(IForensicServiceCommandCallback callback);
+ @EnforcePermission("MANAGE_FORENSIC_STATE")
void disable(IForensicServiceCommandCallback callback);
}
diff --git a/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl b/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl
index 7fa0c7f72690..6d1456ea0426 100644
--- a/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl
+++ b/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl
@@ -25,8 +25,8 @@ package android.security.forensic;
UNKNOWN = 0,
PERMISSION_DENIED = 1,
INVALID_STATE_TRANSITION = 2,
- BACKUP_TRANSPORT_UNAVAILABLE = 3,
- DATA_SOURCE_UNAVAILABLE = 3,
+ TRANSPORT_UNAVAILABLE = 3,
+ DATA_SOURCE_UNAVAILABLE = 4,
}
void onSuccess();
void onFailure(ErrorCode error);
diff --git a/core/java/android/security/forensic/IForensicServiceStateCallback.aidl b/core/java/android/security/forensic/IForensicServiceStateCallback.aidl
index 0cda35083ffd..1b68c7b14bca 100644
--- a/core/java/android/security/forensic/IForensicServiceStateCallback.aidl
+++ b/core/java/android/security/forensic/IForensicServiceStateCallback.aidl
@@ -23,9 +23,8 @@ package android.security.forensic;
@Backing(type="int")
enum State{
UNKNOWN = 0,
- INVISIBLE = 1,
- VISIBLE = 2,
- ENABLED = 3,
+ DISABLED = 1,
+ ENABLED = 2,
}
void onStateChange(State state);
}
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 98dda1031eff..14a14e69f208 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -16,8 +16,10 @@
package android.service.autofill;
+import static android.service.autofill.Flags.FLAG_AUTOFILL_W_METRICS;
import static android.view.autofill.Helper.sVerbose;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -63,14 +65,21 @@ public final class FillEventHistory implements Parcelable {
private static final String TAG = "FillEventHistory";
/**
- * Not in parcel. The ID of the autofill session that created the {@link FillResponse}.
+ * The ID of the autofill session that created the {@link FillResponse}.
+ *
+ * TODO: add this to the parcel.
*/
private final int mSessionId;
@Nullable private final Bundle mClientState;
@Nullable List<Event> mEvents;
- /** @hide */
+ /**
+ * Returns the unique identifier of this FillEventHistory.
+ *
+ * <p>This is used to differentiate individual FillEventHistory.
+ */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
public int getSessionId() {
return mSessionId;
}
@@ -283,6 +292,13 @@ public final class FillEventHistory implements Parcelable {
/** All fields matched contents of datasets. */
public static final int NO_SAVE_UI_REASON_DATASET_MATCH = 6;
+ /**
+ * Credential Manager is invoked instead of Autofill. When that happens, Save Dialog cannot
+ * be shown, and this will be populated in
+ */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ public static final int NO_SAVE_UI_REASON_USING_CREDMAN = 7;
+
/** @hide */
@IntDef(prefix = { "NO_SAVE_UI_REASON_" }, value = {
NO_SAVE_UI_REASON_NONE,
@@ -310,11 +326,20 @@ public final class FillEventHistory implements Parcelable {
public static final int UI_TYPE_DIALOG = 3;
/**
- * The autofill suggestion is shown os a credman bottom sheet
- * @hide
+ * The autofill suggestion is shown os a credman bottom sheet
+ *
+ * <p>Note, this was introduced as bottom sheet even though it applies to all credman UI
+ * types. Instead of exposing this directly to the public, the generic UI_TYPE_CREDMAN is
+ * introduced with the same number.
+ *
+ * @hide
*/
public static final int UI_TYPE_CREDMAN_BOTTOM_SHEET = 4;
+ /** Credential Manager suggestions are shown instead of Autofill suggestion */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ public static final int UI_TYPE_CREDMAN = 4;
+
/** @hide */
@IntDef(prefix = { "UI_TYPE_" }, value = {
UI_TYPE_UNKNOWN,
@@ -359,6 +384,13 @@ public final class FillEventHistory implements Parcelable {
return mEventType;
}
+ /** Gets the {@code AutofillId} that's focused at the time of action */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ @Nullable
+ public AutofillId getFocusedId() {
+ return null;
+ }
+
/**
* Returns the id of dataset the id was on.
*
@@ -391,6 +423,17 @@ public final class FillEventHistory implements Parcelable {
}
/**
+ * Returns which datasets were shown to the user.
+ *
+ * <p><b>Note: </b>Only set on events of type {@link #TYPE_DATASETS_SHOWN}.
+ */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ @NonNull
+ public Set<String> getShownDatasetIds() {
+ return Collections.emptySet();
+ }
+
+ /**
* Returns which datasets were NOT selected by the user.
*
* <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
diff --git a/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl b/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl
index 0dca78d890a5..03e27b866b7a 100644
--- a/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl
+++ b/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl
@@ -43,4 +43,6 @@ interface IQuickAccessWalletService {
oneway void unregisterWalletServiceEventListener(in WalletServiceEventListenerRequest request);
// Request to get a PendingIntent to launch an activity from which the user can manage their cards.
oneway void onTargetActivityIntentRequested(in IQuickAccessWalletServiceCallbacks callbacks);
+ // Request to get a PendingIntent to launch an activity, triggered when the user performs a gesture.
+ oneway void onGestureTargetActivityIntentRequested(in IQuickAccessWalletServiceCallbacks callbacks);
} \ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl b/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl
index 1b69ca12da3a..61d7fd1f526c 100644
--- a/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl
+++ b/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl
@@ -37,4 +37,6 @@ interface IQuickAccessWalletServiceCallbacks {
oneway void onWalletServiceEvent(in WalletServiceEvent event);
// Called in response to onTargetActivityIntentRequested. May only be called once per request.
oneway void onTargetActivityPendingIntentReceived(in PendingIntent pendingIntent);
+ //Called in response to onGesturePendingIntent
+ oneway void onGestureTargetActivityPendingIntentReceived(in PendingIntent pendingIntent);
} \ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
index faa5b2fe3488..b5251db4e539 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
@@ -17,6 +17,7 @@
package android.service.quickaccesswallet;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -181,6 +182,23 @@ public interface QuickAccessWalletClient extends Closeable {
}
/**
+ * Gets the {@link PendingIntent} provided by QuickAccessWalletService to be sent when the user
+ * launches Wallet via gesture.
+ */
+ @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ void getGestureTargetActivityPendingIntent(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull GesturePendingIntentCallback gesturePendingIntentCallback);
+
+ /** Callback interface for getGesturePendingIntent. */
+ @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ interface GesturePendingIntentCallback {
+ /** Callback method for getGesturePendingIntent */
+ @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ void onGesturePendingIntentRetrieved(@Nullable PendingIntent pendingIntent);
+ }
+
+ /**
* The manifest entry for the QuickAccessWalletService may also publish information about the
* activity that hosts the Wallet view. This is typically the home screen of the Wallet
* application.
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
index a59f026c4182..97a4beff633f 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
@@ -267,6 +267,34 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser
}
@Override
+ public void getGestureTargetActivityPendingIntent(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull GesturePendingIntentCallback gesturePendingIntentCallback) {
+ BaseCallbacks callbacks =
+ new BaseCallbacks() {
+ @Override
+ public void onGestureTargetActivityPendingIntentReceived(
+ PendingIntent pendingIntent) {
+ if (!Flags.launchWalletOptionOnPowerDoubleTap()) {
+ return;
+ }
+ executor.execute(
+ () ->
+ gesturePendingIntentCallback
+ .onGesturePendingIntentRetrieved(pendingIntent));
+ }
+ };
+
+ executeApiCall(
+ new ApiCaller("getGestureTargetActivityPendingIntent") {
+ @Override
+ void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+ service.onGestureTargetActivityIntentRequested(callbacks);
+ }
+ });
+ }
+
+ @Override
@Nullable
public Intent createWalletSettingsIntent() {
if (mServiceInfo == null) {
@@ -506,5 +534,9 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser
public void onTargetActivityPendingIntentReceived(PendingIntent pendingIntent) {
throw new IllegalStateException();
}
+
+ public void onGestureTargetActivityPendingIntentReceived(PendingIntent pendingIntent) {
+ throw new IllegalStateException();
+ }
}
}
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
index 36fa21c19d27..90136ae00f6a 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
@@ -16,6 +16,9 @@
package android.service.quickaccesswallet;
+import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -247,6 +250,19 @@ public abstract class QuickAccessWalletService extends Service {
callbacks));
}
+ @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ @Override
+ public void onGestureTargetActivityIntentRequested(
+ @NonNull IQuickAccessWalletServiceCallbacks callbacks) {
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ mHandler.post(
+ () ->
+ QuickAccessWalletService.this
+ .onGestureTargetActivityIntentRequestedInternal(
+ callbacks));
+ }
+ }
+
public void registerWalletServiceEventListener(
@NonNull WalletServiceEventListenerRequest request,
@NonNull IQuickAccessWalletServiceCallbacks callback) {
@@ -275,6 +291,20 @@ public abstract class QuickAccessWalletService extends Service {
}
}
+ private void onGestureTargetActivityIntentRequestedInternal(
+ IQuickAccessWalletServiceCallbacks callbacks) {
+ if (!Flags.launchWalletOptionOnPowerDoubleTap()) {
+ return;
+ }
+
+ try {
+ callbacks.onGestureTargetActivityPendingIntentReceived(
+ getGestureTargetActivityPendingIntent());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error", e);
+ }
+ }
+
@Override
@Nullable
public IBinder onBind(@NonNull Intent intent) {
@@ -349,6 +379,18 @@ public abstract class QuickAccessWalletService extends Service {
return null;
}
+ /**
+ * Specify a {@link PendingIntent} to be launched on user gesture.
+ *
+ * <p>The pending intent will be sent when the user performs a gesture to open Wallet.
+ * The pending intent should launch an activity.
+ */
+ @Nullable
+ @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public PendingIntent getGestureTargetActivityPendingIntent() {
+ return null;
+ }
+
private void sendWalletServiceEventInternal(WalletServiceEvent serviceEvent) {
if (mEventListener == null) {
Log.i(TAG, "No dismiss listener registered");
diff --git a/core/java/android/service/quickaccesswallet/flags.aconfig b/core/java/android/service/quickaccesswallet/flags.aconfig
index 07311d5ffbe1..75a93091eec3 100644
--- a/core/java/android/service/quickaccesswallet/flags.aconfig
+++ b/core/java/android/service/quickaccesswallet/flags.aconfig
@@ -3,7 +3,7 @@ container: "system"
flag {
name: "launch_wallet_option_on_power_double_tap"
- namespace: "wallet_integrations"
+ namespace: "wallet_integration"
description: "Option to launch the Wallet app on double-tap of the power button"
bug: "378469025"
} \ No newline at end of file
diff --git a/core/java/android/telephony/SubscriptionPlan.java b/core/java/android/telephony/SubscriptionPlan.java
index 7b48a16c2227..4c59a8589df2 100644
--- a/core/java/android/telephony/SubscriptionPlan.java
+++ b/core/java/android/telephony/SubscriptionPlan.java
@@ -18,6 +18,7 @@ package android.telephony;
import android.annotation.BytesLong;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,6 +29,7 @@ import android.telephony.Annotation.NetworkType;
import android.util.Range;
import android.util.RecurrenceRule;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
@@ -83,6 +85,33 @@ public final class SubscriptionPlan implements Parcelable {
/** Value indicating a timestamp is unknown. */
public static final long TIME_UNKNOWN = -1;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "SUBSCRIPTION_STATUS_" }, value = {
+ SUBSCRIPTION_STATUS_UNKNOWN,
+ SUBSCRIPTION_STATUS_ACTIVE,
+ SUBSCRIPTION_STATUS_INACTIVE,
+ SUBSCRIPTION_STATUS_TRIAL,
+ SUBSCRIPTION_STATUS_SUSPENDED
+ })
+ public @interface SubscriptionStatus {}
+
+ /** Subscription status is unknown. */
+ @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+ public static final int SUBSCRIPTION_STATUS_UNKNOWN = 0;
+ /** Subscription is active. */
+ @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+ public static final int SUBSCRIPTION_STATUS_ACTIVE = 1;
+ /** Subscription is inactive. */
+ @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+ public static final int SUBSCRIPTION_STATUS_INACTIVE = 2;
+ /** Subscription is in a trial period. */
+ @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+ public static final int SUBSCRIPTION_STATUS_TRIAL = 3;
+ /** Subscription is suspended. */
+ @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+ public static final int SUBSCRIPTION_STATUS_SUSPENDED = 4;
+
private final RecurrenceRule cycleRule;
private CharSequence title;
private CharSequence summary;
@@ -91,6 +120,7 @@ public final class SubscriptionPlan implements Parcelable {
private long dataUsageBytes = BYTES_UNKNOWN;
private long dataUsageTime = TIME_UNKNOWN;
private @NetworkType int[] networkTypes;
+ private int mSubscriptionStatus = SUBSCRIPTION_STATUS_UNKNOWN;
private SubscriptionPlan(RecurrenceRule cycleRule) {
this.cycleRule = Preconditions.checkNotNull(cycleRule);
@@ -107,6 +137,7 @@ public final class SubscriptionPlan implements Parcelable {
dataUsageBytes = source.readLong();
dataUsageTime = source.readLong();
networkTypes = source.createIntArray();
+ mSubscriptionStatus = source.readInt();
}
@Override
@@ -124,6 +155,7 @@ public final class SubscriptionPlan implements Parcelable {
dest.writeLong(dataUsageBytes);
dest.writeLong(dataUsageTime);
dest.writeIntArray(networkTypes);
+ dest.writeInt(mSubscriptionStatus);
}
@Override
@@ -137,13 +169,14 @@ public final class SubscriptionPlan implements Parcelable {
.append(" dataUsageBytes=").append(dataUsageBytes)
.append(" dataUsageTime=").append(dataUsageTime)
.append(" networkTypes=").append(Arrays.toString(networkTypes))
+ .append(" subscriptionStatus=").append(mSubscriptionStatus)
.append("}").toString();
}
@Override
public int hashCode() {
return Objects.hash(cycleRule, title, summary, dataLimitBytes, dataLimitBehavior,
- dataUsageBytes, dataUsageTime, Arrays.hashCode(networkTypes));
+ dataUsageBytes, dataUsageTime, Arrays.hashCode(networkTypes), mSubscriptionStatus);
}
@Override
@@ -157,7 +190,8 @@ public final class SubscriptionPlan implements Parcelable {
&& dataLimitBehavior == other.dataLimitBehavior
&& dataUsageBytes == other.dataUsageBytes
&& dataUsageTime == other.dataUsageTime
- && Arrays.equals(networkTypes, other.networkTypes);
+ && Arrays.equals(networkTypes, other.networkTypes)
+ && mSubscriptionStatus == other.mSubscriptionStatus;
}
return false;
}
@@ -179,6 +213,13 @@ public final class SubscriptionPlan implements Parcelable {
return cycleRule;
}
+ /** Return the end date of this plan, or null if no end date exists. */
+ @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+ public @Nullable ZonedDateTime getPlanEndDate() {
+ // ZonedDateTime is immutable, so no need to create a defensive copy.
+ return cycleRule.end;
+ }
+
/** Return the short title of this plan. */
public @Nullable CharSequence getTitle() {
return title;
@@ -238,6 +279,16 @@ public final class SubscriptionPlan implements Parcelable {
}
/**
+ * Returns the status of the subscription plan.
+ *
+ * @return The subscription status, or {@link #SUBSCRIPTION_STATUS_UNKNOWN} if not available.
+ */
+ @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+ public @SubscriptionStatus int getSubscriptionStatus() {
+ return mSubscriptionStatus;
+ }
+
+ /**
* Builder for a {@link SubscriptionPlan}.
*/
public static class Builder {
@@ -382,5 +433,21 @@ public final class SubscriptionPlan implements Parcelable {
TelephonyManager.getAllNetworkTypes().length);
return this;
}
+
+ /**
+ * Set the subscription status.
+ *
+ * @param subscriptionStatus the current subscription status
+ */
+ @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+ public @NonNull Builder setSubscriptionStatus(@SubscriptionStatus int subscriptionStatus) {
+ if (subscriptionStatus < SUBSCRIPTION_STATUS_UNKNOWN
+ || subscriptionStatus > SUBSCRIPTION_STATUS_SUSPENDED) {
+ throw new IllegalArgumentException(
+ "Subscription status must be defined with a valid value");
+ }
+ plan.mSubscriptionStatus = subscriptionStatus;
+ return this;
+ }
}
}
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
deleted file mode 100644
index f69a333ff81f..000000000000
--- a/core/java/android/text/TextFlags.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import android.annotation.NonNull;
-import android.app.AppGlobals;
-
-/**
- * Flags in the "text" namespace.
- *
- * TODO(nona): Remove this class.
- * @hide
- */
-public final class TextFlags {
-
- /**
- * The name space of the "text" feature.
- *
- * This needs to move to DeviceConfig constant.
- */
- public static final String NAMESPACE = "text";
-
- /**
- * Whether we use the new design of context menu.
- */
- public static final String ENABLE_NEW_CONTEXT_MENU =
- "TextEditing__enable_new_context_menu";
-
- /**
- * The key name used in app core settings for {@link #ENABLE_NEW_CONTEXT_MENU}.
- */
- public static final String KEY_ENABLE_NEW_CONTEXT_MENU = "text__enable_new_context_menu";
-
- /**
- * Default value for the flag {@link #ENABLE_NEW_CONTEXT_MENU}.
- */
- public static final boolean ENABLE_NEW_CONTEXT_MENU_DEFAULT = true;
-
- /**
- * List of text flags to be transferred to the application process.
- */
- public static final String[] TEXT_ACONFIGS_FLAGS = {
- };
-
- /**
- * List of the default values of the text flags.
- *
- * The order must be the same to the TEXT_ACONFIG_FLAGS.
- */
- public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = {
- };
-
- /**
- * Get a key for the feature flag.
- */
- public static String getKeyForFlag(@NonNull String flag) {
- return "text__" + flag;
- }
-
- /**
- * Return true if the feature flag is enabled.
- */
- public static boolean isFeatureEnabled(@NonNull String flag) {
- return AppGlobals.getIntCoreSetting(
- getKeyForFlag(flag), 0 /* aconfig is false by default */) != 0;
- }
-}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 032f5923d3f2..cb72b976c784 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -50,6 +50,7 @@ import android.text.style.LineBackgroundSpan;
import android.text.style.LineBreakConfigSpan;
import android.text.style.LineHeightSpan;
import android.text.style.LocaleSpan;
+import android.text.style.NoWritingToolsSpan;
import android.text.style.ParagraphStyle;
import android.text.style.QuoteSpan;
import android.text.style.RelativeSizeSpan;
@@ -817,7 +818,9 @@ public class TextUtils {
/** @hide */
public static final int LINE_BREAK_CONFIG_SPAN = 30;
/** @hide */
- public static final int LAST_SPAN = LINE_BREAK_CONFIG_SPAN;
+ public static final int NO_WRITING_TOOLS_SPAN = 31;
+ /** @hide */
+ public static final int LAST_SPAN = NO_WRITING_TOOLS_SPAN;
/**
* Flatten a CharSequence and whatever styles can be copied across processes
@@ -1025,6 +1028,10 @@ public class TextUtils {
span = LineBreakConfigSpan.CREATOR.createFromParcel(p);
break;
+ case NO_WRITING_TOOLS_SPAN:
+ span = NoWritingToolsSpan.CREATOR.createFromParcel(p);
+ break;
+
default:
throw new RuntimeException("bogus span encoding " + kind);
}
diff --git a/core/java/android/text/style/NoWritingToolsSpan.java b/core/java/android/text/style/NoWritingToolsSpan.java
new file mode 100644
index 000000000000..90f85aa69faa
--- /dev/null
+++ b/core/java/android/text/style/NoWritingToolsSpan.java
@@ -0,0 +1,87 @@
+/*
+ * 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.text.style;
+
+import static android.view.inputmethod.Flags.FLAG_WRITING_TOOLS;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
+
+/**
+ * A span that signals to IMEs that writing tools should not modify the text.
+ *
+ * <p>For example, a text field may contain a mix of user input text and quoted text. The app
+ * can apply {@code NoWritingToolsSpan} to the quoted text so that the IME knows that writing
+ * tools should only rewrite the user input text, and not modify the quoted text.
+ */
+@FlaggedApi(FLAG_WRITING_TOOLS)
+public final class NoWritingToolsSpan implements ParcelableSpan {
+
+ /**
+ * Creates a {@link NoWritingToolsSpan}.
+ */
+ public NoWritingToolsSpan() {
+ }
+
+ @Override
+ public int getSpanTypeId() {
+ return getSpanTypeIdInternal();
+ }
+
+ /** @hide */
+ @Override
+ public int getSpanTypeIdInternal() {
+ return TextUtils.NO_WRITING_TOOLS_SPAN;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ writeToParcelInternal(dest, flags);
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcelInternal(@NonNull Parcel dest, int flags) {
+ }
+
+ @Override
+ public String toString() {
+ return "NoWritingToolsSpan{}";
+ }
+
+ @NonNull
+ public static final Creator<NoWritingToolsSpan> CREATOR = new Creator<>() {
+
+ @Override
+ public NoWritingToolsSpan createFromParcel(Parcel source) {
+ return new NoWritingToolsSpan();
+ }
+
+ @Override
+ public NoWritingToolsSpan[] newArray(int size) {
+ return new NoWritingToolsSpan[size];
+ }
+ };
+}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 5a71282dab0a..8cb96ae1d611 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -930,8 +930,8 @@ public final class Choreographer {
// of the next vsync event.
int totalFrameDelays = mBufferStuffingData.numberFrameDelays
+ mBufferStuffingData.numberWaitsForNextVsync + 1;
- long vsyncsSinceLastCallback =
- (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos;
+ long vsyncsSinceLastCallback = mLastFrameIntervalNanos > 0
+ ? (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos : 0;
// Detected idle state due to a longer inactive period since the last vsync callback
// than the total expected number of vsync frame delays. End buffer stuffing recovery.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 68efa79715a3..d56768d2db03 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -4566,14 +4566,31 @@ public final class SurfaceControl implements Parcelable {
return this;
}
- /** @hide */
+ /**
+ * Sets the Luts for the layer.
+ *
+ * <p> The function also allows to clear previously applied lut(s). To do this,
+ * set the displayluts to be either {@code nullptr} or
+ * an empty {@link android.hardware.DisplayLuts} instance.
+ *
+ * @param sc The SurfaceControl to update
+ *
+ * @param displayLuts The selected Lut(s)
+ *
+ * @return this
+ * @see DisplayLuts
+ */
+ @FlaggedApi(android.hardware.flags.Flags.FLAG_LUTS_API)
public @NonNull Transaction setLuts(@NonNull SurfaceControl sc,
- @NonNull DisplayLuts displayLuts) {
+ @Nullable DisplayLuts displayLuts) {
checkPreconditions(sc);
-
- nativeSetLuts(mNativeObject, sc.mNativeObject, displayLuts.getLutBuffers(),
- displayLuts.getOffsets(), displayLuts.getLutDimensions(),
- displayLuts.getLutSizes(), displayLuts.getLutSamplingKeys());
+ if (displayLuts != null && displayLuts.valid()) {
+ nativeSetLuts(mNativeObject, sc.mNativeObject, displayLuts.getLutBuffers(),
+ displayLuts.getOffsets(), displayLuts.getLutDimensions(),
+ displayLuts.getLutSizes(), displayLuts.getLutSamplingKeys());
+ } else {
+ nativeSetLuts(mNativeObject, sc.mNativeObject, null, null, null, null, null);
+ }
return this;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e50662adc3f1..19d3dc4df04e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2516,6 +2516,11 @@ public final class ViewRootImpl implements ViewParent,
public void notifyInsetsAnimationRunningStateChanged(boolean running) {
if (sToolkitSetFrameRateReadOnlyFlagValue) {
mInsetsAnimationRunning = running;
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instant(Trace.TRACE_TAG_VIEW,
+ TextUtils.formatSimple("notifyInsetsAnimationRunningStateChanged(%s)",
+ Boolean.toString(running)));
+ }
}
}
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 1af9387e6fbd..1be7f4849f07 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -17,6 +17,7 @@
package android.view;
import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION;
+import static android.service.autofill.Flags.FLAG_AUTOFILL_W_METRICS;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
@@ -79,27 +80,24 @@ public abstract class ViewStructure {
* Key used for writing the type of the view that generated the virtual structure of its
* children.
*
- * For example, if the virtual structure is generated by a webview, the value would be
+ * <p>For example, if the virtual structure is generated by a webview, the value would be
* "WebView". If the virtual structure is generated by a compose view, then the value would be
* "ComposeView". The value is of type String.
*
- * This value is added to mainly help with debugging purpose.
- *
- * @hide
+ * <p>This value is added to mainly help with debugging purpose.
*/
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE =
"android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE";
-
/**
* Key used for specifying the version of the view that generated the virtual structure for
* itself and its children
*
- * For example, if the virtual structure is generated by a webview of version "104.0.5112.69",
- * then the value should be "104.0.5112.69"
- *
- * @hide
+ * <p>For example, if the virtual structure is generated by a webview of version
+ * "104.0.5112.69", then the value should be "104.0.5112.69"
*/
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER =
"android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
index 6b608582c1dd..bd277784c1d3 100644
--- a/core/java/android/view/autofill/AutofillId.java
+++ b/core/java/android/view/autofill/AutofillId.java
@@ -15,6 +15,9 @@
*/
package android.view.autofill;
+import static android.service.autofill.Flags.FLAG_AUTOFILL_W_METRICS;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -111,12 +114,43 @@ public final class AutofillId implements Parcelable {
return new AutofillId(flags, id.mViewId, virtualChildId, NO_SESSION);
}
- /** @hide */
+ /**
+ * Returns the assigned unique identifier of this AutofillID.
+ *
+ * See @link{android.view.View#getAutofillId()} for more information on
+ * how this is generated for native Views.
+ */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
public int getViewId() {
return mViewId;
}
/**
+ * Gets the virtual id. This is set if the view is a virtual view, most commonly set if the View
+ * is of {@link android.webkit.WebView}.
+ */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ public int getAutofillVirtualId() {
+ return mVirtualIntId;
+ }
+
+ /** Checks whether this AutofillId represents a virtual view. */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ public boolean isVirtual() {
+ return !isNonVirtual();
+ }
+
+ /**
+ * Checks if this node is generate as part of a {@link android.app.assist.AssistStructure}. This
+ * will usually return true if it should be used by an autofill service provider, and false
+ * otherwise.
+ */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ public boolean isInAutofillSession() {
+ return hasSession();
+ }
+
+ /**
* Gets the virtual child id.
*
* <p>Should only be used on subsystems where such id is represented by an {@code int}
@@ -181,7 +215,12 @@ public final class AutofillId implements Parcelable {
return (mFlags & FLAG_HAS_SESSION) != 0;
}
- /** @hide */
+ /**
+ * Used to get the Session identifier associated with this AutofillId.
+ *
+ * @return a non-zero integer if {@link #isInAutofillSession()} returns true
+ */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
public int getSessionId() {
return mSessionId;
}
diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig
index b180e58cbe49..ebda4d472b0d 100644
--- a/core/java/android/view/flags/scroll_feedback_flags.aconfig
+++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig
@@ -28,5 +28,5 @@ flag {
namespace: "wear_frameworks"
name: "dynamic_view_rotary_haptics_configuration"
description: "Whether ScrollFeedbackProvider dynamically disables View-based rotary haptics."
- bug: "377998870 "
+ bug: "377998870"
}
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index a5603399142b..afe195c48b01 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -523,7 +523,6 @@ public class EditorInfo implements InputType, Parcelable {
@Nullable
public LocaleList hintLocales = null;
-
/**
* List of acceptable MIME types for
* {@link InputConnection#commitContent(InputContentInfo, int, Bundle)}.
@@ -758,6 +757,30 @@ public class EditorInfo implements InputType, Parcelable {
return mIsStylusHandwritingEnabled;
}
+ private boolean mWritingToolsEnabled = true;
+
+ /**
+ * Returns {@code true} when an {@code Editor} has writing tools enabled.
+ * {@code true} by default for all editors. Toolkits can optionally disable them where not
+ * relevant e.g. passwords, number input, etc.
+ * @see #setWritingToolsEnabled(boolean)
+ */
+ @FlaggedApi(Flags.FLAG_WRITING_TOOLS)
+ public boolean isWritingToolsEnabled() {
+ return mWritingToolsEnabled;
+ }
+
+ /**
+ * Set {@code false} if {@code Editor} opts-out of writing tools, that enable IMEs to replace
+ * text with generative AI text.
+ * @param enabled set {@code true} to enabled or {@code false to disable} support.
+ * @see #isWritingToolsEnabled()
+ */
+ @FlaggedApi(Flags.FLAG_WRITING_TOOLS)
+ public void setWritingToolsEnabled(boolean enabled) {
+ mWritingToolsEnabled = enabled;
+ }
+
/**
* If not {@code null}, this editor needs to talk to IMEs that run for the specified user, no
* matter what user ID the calling process has.
@@ -1276,6 +1299,7 @@ public class EditorInfo implements InputType, Parcelable {
+ InputMethodDebug.handwritingGestureTypeFlagsToString(
mSupportedHandwritingGesturePreviewTypes));
pw.println(prefix + "isStylusHandwritingEnabled=" + mIsStylusHandwritingEnabled);
+ pw.println(prefix + "writingToolsEnabled=" + mWritingToolsEnabled);
pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes));
if (targetInputMethodUser != null) {
pw.println(prefix + "targetInputMethodUserId=" + targetInputMethodUser.getIdentifier());
@@ -1356,6 +1380,7 @@ public class EditorInfo implements InputType, Parcelable {
}
dest.writeStringArray(contentMimeTypes);
UserHandle.writeToParcel(targetInputMethodUser, dest);
+ dest.writeBoolean(mWritingToolsEnabled);
}
/**
@@ -1396,6 +1421,7 @@ public class EditorInfo implements InputType, Parcelable {
res.hintLocales = hintLocales.isEmpty() ? null : hintLocales;
res.contentMimeTypes = source.readStringArray();
res.targetInputMethodUser = UserHandle.readFromParcel(source);
+ res.mWritingToolsEnabled = source.readBoolean();
return res;
}
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index e619ab064005..deaf95797127 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -175,3 +175,12 @@ flag {
bug: "342672560"
is_fixed_read_only: true
}
+
+flag {
+ name: "adaptive_handwriting_bounds"
+ is_exported: true
+ namespace: "input_method"
+ description: "Feature flag for adaptively increasing handwriting bounds."
+ bug: "350047836"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d7750bd412a3..cb70466fcd81 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -106,6 +106,7 @@ import android.os.Parcelable;
import android.os.ParcelableParcel;
import android.os.Process;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.BoringLayout;
@@ -9229,174 +9230,179 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
protected void onDraw(Canvas canvas) {
- restartMarqueeIfNeeded();
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "TextView.onDraw");
+ try {
+ restartMarqueeIfNeeded();
- // Draw the background for this view
- super.onDraw(canvas);
-
- final int compoundPaddingLeft = getCompoundPaddingLeft();
- final int compoundPaddingTop = getCompoundPaddingTop();
- final int compoundPaddingRight = getCompoundPaddingRight();
- final int compoundPaddingBottom = getCompoundPaddingBottom();
- final int scrollX = mScrollX;
- final int scrollY = mScrollY;
- final int right = mRight;
- final int left = mLeft;
- final int bottom = mBottom;
- final int top = mTop;
- final boolean isLayoutRtl = isLayoutRtl();
- final int offset = getHorizontalOffsetForDrawables();
- final int leftOffset = isLayoutRtl ? 0 : offset;
- final int rightOffset = isLayoutRtl ? offset : 0;
+ // Draw the background for this view
+ super.onDraw(canvas);
- final Drawables dr = mDrawables;
- if (dr != null) {
- /*
- * Compound, not extended, because the icon is not clipped
- * if the text height is smaller.
- */
+ final int compoundPaddingLeft = getCompoundPaddingLeft();
+ final int compoundPaddingTop = getCompoundPaddingTop();
+ final int compoundPaddingRight = getCompoundPaddingRight();
+ final int compoundPaddingBottom = getCompoundPaddingBottom();
+ final int scrollX = mScrollX;
+ final int scrollY = mScrollY;
+ final int right = mRight;
+ final int left = mLeft;
+ final int bottom = mBottom;
+ final int top = mTop;
+ final boolean isLayoutRtl = isLayoutRtl();
+ final int offset = getHorizontalOffsetForDrawables();
+ final int leftOffset = isLayoutRtl ? 0 : offset;
+ final int rightOffset = isLayoutRtl ? offset : 0;
- int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
- int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
+ final Drawables dr = mDrawables;
+ if (dr != null) {
+ /*
+ * Compound, not extended, because the icon is not clipped
+ * if the text height is smaller.
+ */
- // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
- // Make sure to update invalidateDrawable() when changing this code.
- if (dr.mShowing[Drawables.LEFT] != null) {
- canvas.save();
- canvas.translate(scrollX + mPaddingLeft + leftOffset,
- scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
- dr.mShowing[Drawables.LEFT].draw(canvas);
- canvas.restore();
- }
+ int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
+ int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
+
+ // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+ // Make sure to update invalidateDrawable() when changing this code.
+ if (dr.mShowing[Drawables.LEFT] != null) {
+ canvas.save();
+ canvas.translate(scrollX + mPaddingLeft + leftOffset,
+ scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
+ dr.mShowing[Drawables.LEFT].draw(canvas);
+ canvas.restore();
+ }
- // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
- // Make sure to update invalidateDrawable() when changing this code.
- if (dr.mShowing[Drawables.RIGHT] != null) {
- canvas.save();
- canvas.translate(scrollX + right - left - mPaddingRight
- - dr.mDrawableSizeRight - rightOffset,
- scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
- dr.mShowing[Drawables.RIGHT].draw(canvas);
- canvas.restore();
- }
+ // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+ // Make sure to update invalidateDrawable() when changing this code.
+ if (dr.mShowing[Drawables.RIGHT] != null) {
+ canvas.save();
+ canvas.translate(scrollX + right - left - mPaddingRight
+ - dr.mDrawableSizeRight - rightOffset,
+ scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
+ dr.mShowing[Drawables.RIGHT].draw(canvas);
+ canvas.restore();
+ }
- // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
- // Make sure to update invalidateDrawable() when changing this code.
- if (dr.mShowing[Drawables.TOP] != null) {
- canvas.save();
- canvas.translate(scrollX + compoundPaddingLeft
- + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
- dr.mShowing[Drawables.TOP].draw(canvas);
- canvas.restore();
- }
+ // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+ // Make sure to update invalidateDrawable() when changing this code.
+ if (dr.mShowing[Drawables.TOP] != null) {
+ canvas.save();
+ canvas.translate(scrollX + compoundPaddingLeft
+ + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
+ dr.mShowing[Drawables.TOP].draw(canvas);
+ canvas.restore();
+ }
- // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
- // Make sure to update invalidateDrawable() when changing this code.
- if (dr.mShowing[Drawables.BOTTOM] != null) {
- canvas.save();
- canvas.translate(scrollX + compoundPaddingLeft
- + (hspace - dr.mDrawableWidthBottom) / 2,
- scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
- dr.mShowing[Drawables.BOTTOM].draw(canvas);
- canvas.restore();
+ // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+ // Make sure to update invalidateDrawable() when changing this code.
+ if (dr.mShowing[Drawables.BOTTOM] != null) {
+ canvas.save();
+ canvas.translate(scrollX + compoundPaddingLeft
+ + (hspace - dr.mDrawableWidthBottom) / 2,
+ scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
+ dr.mShowing[Drawables.BOTTOM].draw(canvas);
+ canvas.restore();
+ }
}
- }
- int color = mCurTextColor;
+ int color = mCurTextColor;
- if (mLayout == null) {
- assumeLayout();
- }
+ if (mLayout == null) {
+ assumeLayout();
+ }
+
+ Layout layout = mLayout;
- Layout layout = mLayout;
+ if (mHint != null && !mHideHint && mText.length() == 0) {
+ if (mHintTextColor != null) {
+ color = mCurHintTextColor;
+ }
- if (mHint != null && !mHideHint && mText.length() == 0) {
- if (mHintTextColor != null) {
- color = mCurHintTextColor;
+ layout = mHintLayout;
}
- layout = mHintLayout;
- }
+ mTextPaint.setColor(color);
+ mTextPaint.drawableState = getDrawableState();
- mTextPaint.setColor(color);
- mTextPaint.drawableState = getDrawableState();
+ canvas.save();
+ /* Would be faster if we didn't have to do this. Can we chop the
+ (displayable) text so that we don't need to do this ever?
+ */
- canvas.save();
- /* Would be faster if we didn't have to do this. Can we chop the
- (displayable) text so that we don't need to do this ever?
- */
+ int extendedPaddingTop = getExtendedPaddingTop();
+ int extendedPaddingBottom = getExtendedPaddingBottom();
- int extendedPaddingTop = getExtendedPaddingTop();
- int extendedPaddingBottom = getExtendedPaddingBottom();
+ final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
+ final int maxScrollY = mLayout.getHeight() - vspace;
- final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
- final int maxScrollY = mLayout.getHeight() - vspace;
+ float clipLeft = compoundPaddingLeft + scrollX;
+ float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
+ float clipRight = right - left - getCompoundPaddingRight() + scrollX;
+ float clipBottom = bottom - top + scrollY
+ - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
- float clipLeft = compoundPaddingLeft + scrollX;
- float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
- float clipRight = right - left - getCompoundPaddingRight() + scrollX;
- float clipBottom = bottom - top + scrollY
- - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
+ if (mShadowRadius != 0) {
+ clipLeft += Math.min(0, mShadowDx - mShadowRadius);
+ clipRight += Math.max(0, mShadowDx + mShadowRadius);
- if (mShadowRadius != 0) {
- clipLeft += Math.min(0, mShadowDx - mShadowRadius);
- clipRight += Math.max(0, mShadowDx + mShadowRadius);
+ clipTop += Math.min(0, mShadowDy - mShadowRadius);
+ clipBottom += Math.max(0, mShadowDy + mShadowRadius);
+ }
- clipTop += Math.min(0, mShadowDy - mShadowRadius);
- clipBottom += Math.max(0, mShadowDy + mShadowRadius);
- }
+ canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
- canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
+ int voffsetText = 0;
+ int voffsetCursor = 0;
- int voffsetText = 0;
- int voffsetCursor = 0;
+ // translate in by our padding
+ /* shortcircuit calling getVerticaOffset() */
+ if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
+ voffsetText = getVerticalOffset(false);
+ voffsetCursor = getVerticalOffset(true);
+ }
+ canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
+
+ final int layoutDirection = getLayoutDirection();
+ final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
+ if (isMarqueeFadeEnabled()) {
+ if (!mSingleLine && getLineCount() == 1 && canMarquee()
+ && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
+ final int width = mRight - mLeft;
+ final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
+ final float dx = mLayout.getLineRight(0) - (width - padding);
+ canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
+ }
- // translate in by our padding
- /* shortcircuit calling getVerticaOffset() */
- if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
- voffsetText = getVerticalOffset(false);
- voffsetCursor = getVerticalOffset(true);
- }
- canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
-
- final int layoutDirection = getLayoutDirection();
- final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
- if (isMarqueeFadeEnabled()) {
- if (!mSingleLine && getLineCount() == 1 && canMarquee()
- && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
- final int width = mRight - mLeft;
- final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
- final float dx = mLayout.getLineRight(0) - (width - padding);
- canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
+ if (mMarquee != null && mMarquee.isRunning()) {
+ final float dx = -mMarquee.getScroll();
+ canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
+ }
}
- if (mMarquee != null && mMarquee.isRunning()) {
- final float dx = -mMarquee.getScroll();
- canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
- }
- }
+ final int cursorOffsetVertical = voffsetCursor - voffsetText;
- final int cursorOffsetVertical = voffsetCursor - voffsetText;
+ maybeUpdateHighlightPaths();
+ // If there is a gesture preview highlight, then the selection or cursor is not drawn.
+ Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
+ if (mEditor != null) {
+ mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
+ mHighlightPaint, cursorOffsetVertical);
+ } else {
+ layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
+ cursorOffsetVertical);
+ }
- maybeUpdateHighlightPaths();
- // If there is a gesture preview highlight, then the selection or cursor is not drawn.
- Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
- if (mEditor != null) {
- mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
- mHighlightPaint, cursorOffsetVertical);
- } else {
- layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
- cursorOffsetVertical);
- }
+ if (mMarquee != null && mMarquee.shouldDrawGhost()) {
+ final float dx = mMarquee.getGhostOffset();
+ canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
+ layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
+ cursorOffsetVertical);
+ }
- if (mMarquee != null && mMarquee.shouldDrawGhost()) {
- final float dx = mMarquee.getGhostOffset();
- canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
- layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
- cursorOffsetVertical);
+ canvas.restore();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
-
- canvas.restore();
}
@Override
@@ -11254,192 +11260,201 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
- int width;
- int height;
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "TextView.onMeasure");
+ try {
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- BoringLayout.Metrics boring = UNKNOWN_BORING;
- BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
+ int width;
+ int height;
- if (mTextDir == null) {
- mTextDir = getTextDirectionHeuristic();
- }
+ BoringLayout.Metrics boring = UNKNOWN_BORING;
+ BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
- int des = -1;
- boolean fromexisting = false;
- final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
- ? (float) widthSize : Float.MAX_VALUE;
-
- if (widthMode == MeasureSpec.EXACTLY) {
- // Parent has told us how big to be. So be it.
- width = widthSize;
- } else {
- if (mLayout != null && mEllipsize == null) {
- des = desired(mLayout, mUseBoundsForWidth);
+ if (mTextDir == null) {
+ mTextDir = getTextDirectionHeuristic();
}
- if (des < 0) {
- boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
- mBoring);
- if (boring != null) {
- mBoring = boring;
- }
- } else {
- fromexisting = true;
- }
+ int des = -1;
+ boolean fromexisting = false;
+ final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
+ ? (float) widthSize : Float.MAX_VALUE;
- if (boring == null || boring == UNKNOWN_BORING) {
- if (des < 0) {
- des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
- mTransformed.length(), mTextPaint, mTextDir, widthLimit,
- mUseBoundsForWidth));
- }
- width = des;
+ if (widthMode == MeasureSpec.EXACTLY) {
+ // Parent has told us how big to be. So be it.
+ width = widthSize;
} else {
- if (mUseBoundsForWidth) {
- RectF bbox = boring.getDrawingBoundingBox();
- float rightMax = Math.max(bbox.right, boring.width);
- float leftMin = Math.min(bbox.left, 0);
- width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin));
- } else {
- width = boring.width;
+ if (mLayout != null && mEllipsize == null) {
+ des = desired(mLayout, mUseBoundsForWidth);
}
- }
-
- final Drawables dr = mDrawables;
- if (dr != null) {
- width = Math.max(width, dr.mDrawableWidthTop);
- width = Math.max(width, dr.mDrawableWidthBottom);
- }
-
- if (mHint != null) {
- int hintDes = -1;
- int hintWidth;
- if (mHintLayout != null && mEllipsize == null) {
- hintDes = desired(mHintLayout, mUseBoundsForWidth);
- }
-
- if (hintDes < 0) {
- hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
+ if (des < 0) {
+ boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
- mHintBoring);
- if (hintBoring != null) {
- mHintBoring = hintBoring;
+ mBoring);
+ if (boring != null) {
+ mBoring = boring;
}
+ } else {
+ fromexisting = true;
}
- if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
- if (hintDes < 0) {
- hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
- mHint.length(), mTextPaint, mTextDir, widthLimit,
+ if (boring == null || boring == UNKNOWN_BORING) {
+ if (des < 0) {
+ des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
+ mTransformed.length(), mTextPaint, mTextDir, widthLimit,
mUseBoundsForWidth));
}
- hintWidth = hintDes;
+ width = des;
} else {
- hintWidth = hintBoring.width;
+ if (mUseBoundsForWidth) {
+ RectF bbox = boring.getDrawingBoundingBox();
+ float rightMax = Math.max(bbox.right, boring.width);
+ float leftMin = Math.min(bbox.left, 0);
+ width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin));
+ } else {
+ width = boring.width;
+ }
}
- if (hintWidth > width) {
- width = hintWidth;
+ final Drawables dr = mDrawables;
+ if (dr != null) {
+ width = Math.max(width, dr.mDrawableWidthTop);
+ width = Math.max(width, dr.mDrawableWidthBottom);
}
- }
- width += getCompoundPaddingLeft() + getCompoundPaddingRight();
+ if (mHint != null) {
+ int hintDes = -1;
+ int hintWidth;
- if (mMaxWidthMode == EMS) {
- width = Math.min(width, mMaxWidth * getLineHeight());
- } else {
- width = Math.min(width, mMaxWidth);
- }
+ if (mHintLayout != null && mEllipsize == null) {
+ hintDes = desired(mHintLayout, mUseBoundsForWidth);
+ }
- if (mMinWidthMode == EMS) {
- width = Math.max(width, mMinWidth * getLineHeight());
- } else {
- width = Math.max(width, mMinWidth);
- }
+ if (hintDes < 0) {
+ hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
+ isFallbackLineSpacingForBoringLayout(),
+ getResolvedMinimumFontMetrics(),
+ mHintBoring);
+ if (hintBoring != null) {
+ mHintBoring = hintBoring;
+ }
+ }
- // Check against our minimum width
- width = Math.max(width, getSuggestedMinimumWidth());
+ if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
+ if (hintDes < 0) {
+ hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
+ mHint.length(), mTextPaint, mTextDir, widthLimit,
+ mUseBoundsForWidth));
+ }
+ hintWidth = hintDes;
+ } else {
+ hintWidth = hintBoring.width;
+ }
- if (widthMode == MeasureSpec.AT_MOST) {
- width = Math.min(widthSize, width);
- }
- }
+ if (hintWidth > width) {
+ width = hintWidth;
+ }
+ }
- int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
- int unpaddedWidth = want;
+ width += getCompoundPaddingLeft() + getCompoundPaddingRight();
- if (mHorizontallyScrolling) want = VERY_WIDE;
+ if (mMaxWidthMode == EMS) {
+ width = Math.min(width, mMaxWidth * getLineHeight());
+ } else {
+ width = Math.min(width, mMaxWidth);
+ }
- int hintWant = want;
- int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
+ if (mMinWidthMode == EMS) {
+ width = Math.max(width, mMinWidth * getLineHeight());
+ } else {
+ width = Math.max(width, mMinWidth);
+ }
- if (mLayout == null) {
- makeNewLayout(want, hintWant, boring, hintBoring,
- width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
- } else {
- final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
- || (mLayout.getEllipsizedWidth()
- != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
+ // Check against our minimum width
+ width = Math.max(width, getSuggestedMinimumWidth());
+
+ if (widthMode == MeasureSpec.AT_MOST) {
+ width = Math.min(widthSize, width);
+ }
+ }
- final boolean widthChanged = (mHint == null) && (mEllipsize == null)
- && (want > mLayout.getWidth())
- && (mLayout instanceof BoringLayout
- || (fromexisting && des >= 0 && des <= want));
+ int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
+ int unpaddedWidth = want;
- final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
+ if (mHorizontallyScrolling) want = VERY_WIDE;
- if (layoutChanged || maximumChanged) {
- if (!maximumChanged && widthChanged) {
- mLayout.increaseWidthTo(want);
+ int hintWant = want;
+ int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
+
+ if (mLayout == null) {
+ makeNewLayout(want, hintWant, boring, hintBoring,
+ width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
+ } else {
+ final boolean layoutChanged =
+ (mLayout.getWidth() != want) || (hintWidth != hintWant)
+ || (mLayout.getEllipsizedWidth()
+ != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
+
+ final boolean widthChanged = (mHint == null) && (mEllipsize == null)
+ && (want > mLayout.getWidth())
+ && (mLayout instanceof BoringLayout
+ || (fromexisting && des >= 0 && des <= want));
+
+ final boolean maximumChanged =
+ (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
+
+ if (layoutChanged || maximumChanged) {
+ if (!maximumChanged && widthChanged) {
+ mLayout.increaseWidthTo(want);
+ } else {
+ makeNewLayout(want, hintWant, boring, hintBoring,
+ width - getCompoundPaddingLeft() - getCompoundPaddingRight(),
+ false);
+ }
} else {
- makeNewLayout(want, hintWant, boring, hintBoring,
- width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
+ // Nothing has changed
}
- } else {
- // Nothing has changed
}
- }
- if (heightMode == MeasureSpec.EXACTLY) {
- // Parent has told us how big to be. So be it.
- height = heightSize;
- mDesiredHeightAtMeasure = -1;
- } else {
- int desired = getDesiredHeight();
+ if (heightMode == MeasureSpec.EXACTLY) {
+ // Parent has told us how big to be. So be it.
+ height = heightSize;
+ mDesiredHeightAtMeasure = -1;
+ } else {
+ int desired = getDesiredHeight();
- height = desired;
- mDesiredHeightAtMeasure = desired;
+ height = desired;
+ mDesiredHeightAtMeasure = desired;
- if (heightMode == MeasureSpec.AT_MOST) {
- height = Math.min(desired, heightSize);
+ if (heightMode == MeasureSpec.AT_MOST) {
+ height = Math.min(desired, heightSize);
+ }
}
- }
- int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
- if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
- unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
- }
+ int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
+ if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
+ unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
+ }
- /*
- * We didn't let makeNewLayout() register to bring the cursor into view,
- * so do it here if there is any possibility that it is needed.
- */
- if (mMovement != null
- || mLayout.getWidth() > unpaddedWidth
- || mLayout.getHeight() > unpaddedHeight) {
- registerForPreDraw();
- } else {
- scrollTo(0, 0);
- }
+ /*
+ * We didn't let makeNewLayout() register to bring the cursor into view,
+ * so do it here if there is any possibility that it is needed.
+ */
+ if (mMovement != null
+ || mLayout.getWidth() > unpaddedWidth
+ || mLayout.getHeight() > unpaddedHeight) {
+ registerForPreDraw();
+ } else {
+ scrollTo(0, 0);
+ }
- setMeasuredDimension(width, height);
+ setMeasuredDimension(width, height);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
}
/**
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 0f2dd10d7f47..2c21417fb790 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -49,6 +49,7 @@ import android.content.ComponentName;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
+import android.os.BinderProxy;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -1089,8 +1090,13 @@ public final class TransitionInfo implements Parcelable {
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
- sb.append('{'); sb.append(mContainer);
- sb.append(" m="); sb.append(modeToString(mMode));
+ sb.append('{');
+ if (mContainer != null && !(mContainer.asBinder() instanceof BinderProxy)) {
+ // Only log the token if it is not a binder proxy and has additional container info
+ sb.append(mContainer);
+ sb.append(" ");
+ }
+ sb.append("m="); sb.append(modeToString(mMode));
sb.append(" f="); sb.append(flagsToString(mFlags));
if (mParent != null) {
sb.append(" p="); sb.append(mParent);
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 3fe63ab17248..a88a17283482 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -1120,8 +1120,8 @@ public final class WindowContainerTransaction implements Parcelable {
@NonNull
public String toString() {
return "WindowContainerTransaction {"
- + " changes = " + mChanges
- + " hops = " + mHierarchyOps
+ + " changes= " + mChanges
+ + " hops= " + mHierarchyOps
+ " errorCallbackToken=" + mErrorCallbackToken
+ " taskFragmentOrganizer=" + mTaskFragmentOrganizer
+ " }";
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index f474b34ac390..eebdeadcdeb2 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -402,4 +402,11 @@ flag {
namespace: "lse_desktop_experience"
description: "Enables HSUM on desktop mode."
bug: "366397912"
+}
+
+flag {
+ name: "enable_multiple_desktops"
+ namespace: "lse_desktop_experience"
+ description: "Enable multiple desktop sessions for desktop windowing."
+ bug: "379158791"
} \ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 4f924a82c9cc..ff69610dbf0e 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -418,6 +418,17 @@ flag {
}
flag {
+ name: "record_task_snapshots_before_shutdown"
+ namespace: "windowing_frontend"
+ description: "Record task snapshots before shutdown"
+ bug: "376821232"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "predictive_back_three_button_nav"
namespace: "systemui"
description: "Enable Predictive Back Animation for 3-button-nav"
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 21c7baab4e83..469ab48385da 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -410,4 +410,15 @@ oneway interface IBackupTransport {
* however backups initiated by the framework will call this method to retrieve one.
*/
void getBackupManagerMonitor(in AndroidFuture<IBackupManagerMonitor> resultFuture);
+
+ /**
+ * Ask the transport whether packages that are about to be backed up or restored should not be
+ * put into a restricted mode by the framework and started normally instead. The
+ * {@code resultFuture} should be completed with a subset of the packages passed in, indicating
+ * which packages should NOT be put into restricted mode for the given operation type.
+ *
+ * @param operationType 0 for backup, 1 for restore.
+ */
+ void getPackagesThatShouldNotUseRestrictedMode(in List<String> packageNames, int operationType,
+ in AndroidFuture<List<String>> resultFuture);
}
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 8a6e6be1abbf..6db3c4dba0e6 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -2377,6 +2377,7 @@ public class ParsingPackageUtils {
* Flags are separated by type and by default value. They are sorted alphabetically within each
* section.
*/
+ @SuppressWarnings("AndroidFrameworkCompatChange")
private void parseBaseAppBasicFlags(ParsingPackage pkg, TypedArray sa) {
int targetSdk = pkg.getTargetSdkVersion();
//@formatter:off
@@ -2414,12 +2415,19 @@ public class ParsingPackageUtils {
.setResetEnabledSettingsOnAppDataCleared(bool(false,
R.styleable.AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared,
sa))
- .setOnBackInvokedCallbackEnabled(bool(false, R.styleable.AndroidManifestApplication_enableOnBackInvokedCallback, sa))
// targetSdkVersion gated
.setAllowAudioPlaybackCapture(bool(targetSdk >= Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, sa))
.setHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
.setRequestLegacyExternalStorage(bool(targetSdk < Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, sa))
.setCleartextTrafficAllowed(bool(targetSdk < Build.VERSION_CODES.P, R.styleable.AndroidManifestApplication_usesCleartextTraffic, sa))
+ // CompatChange.isChangeEnabled() can't be used here because this is called during
+ // PackageManagerService initialization. PlatformCompat can't be used because this
+ // code is not guaranteed to be called from the system_server process. Therefore
+ // accessing Build.VERSION_CODES directly and suppressing
+ // AndroidFrameworkCompatChange warning
+ .setOnBackInvokedCallbackEnabled(bool(
+ targetSdk > Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ R.styleable.AndroidManifestApplication_enableOnBackInvokedCallback, sa))
// Ints Default 0
.setUiOptions(anInt(R.styleable.AndroidManifestApplication_uiOptions, sa))
// Ints
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index a1c987f79304..eb682dff14de 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -676,15 +676,30 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen
return internMap.get(string);
}
+ protected boolean validateGroups(ILogger logger, String[] groups) {
+ for (int i = 0; i < groups.length; i++) {
+ String group = groups[i];
+ IProtoLogGroup g = mLogGroups.get(group);
+ if (g == null) {
+ logger.log("No IProtoLogGroup named " + group);
+ return false;
+ }
+ }
+ return true;
+ }
+
private int setTextLogging(boolean value, ILogger logger, String... groups) {
+ if (!validateGroups(logger, groups)) {
+ return -1;
+ }
+
for (int i = 0; i < groups.length; i++) {
String group = groups[i];
IProtoLogGroup g = mLogGroups.get(group);
if (g != null) {
g.setLogToLogcat(value);
} else {
- logger.log("No IProtoLogGroup named " + group);
- return -1;
+ throw new RuntimeException("No IProtoLogGroup named " + group);
}
}
diff --git a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
index 70d148a311f6..967a5ed1744d 100644
--- a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
@@ -113,6 +113,10 @@ public class ProcessedPerfettoProtoLogImpl extends PerfettoProtoLogImpl {
*/
@Override
public int startLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
+ if (!validateGroups(logger, groups)) {
+ return -1;
+ }
+
mViewerConfigReader.loadViewerConfig(groups, logger);
return super.startLoggingToLogcat(groups, logger);
}
@@ -125,8 +129,19 @@ public class ProcessedPerfettoProtoLogImpl extends PerfettoProtoLogImpl {
*/
@Override
public int stopLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
+ if (!validateGroups(logger, groups)) {
+ return -1;
+ }
+
+ var status = super.stopLoggingToLogcat(groups, logger);
+
+ if (status != 0) {
+ throw new RuntimeException("Failed to stop logging to logcat");
+ }
+
+ // If we successfully disabled logging, unload the viewer config.
mViewerConfigReader.unloadViewerConfig(groups, logger);
- return super.stopLoggingToLogcat(groups, logger);
+ return status;
}
@Deprecated
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
index 3303d875c427..8df3f2abcafd 100644
--- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
+++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
@@ -88,15 +88,19 @@ public final class RavenwoodEnvironment {
/** @hide */
public static class CompatIdsForTest {
// Enabled by default
+ /** Used for testing */
@ChangeId
public static final long TEST_COMPAT_ID_1 = 368131859L;
+ /** Used for testing */
@Disabled
@ChangeId public static final long TEST_COMPAT_ID_2 = 368131701L;
+ /** Used for testing */
@EnabledAfter(targetSdkVersion = S)
@ChangeId public static final long TEST_COMPAT_ID_3 = 368131659L;
+ /** Used for testing */
@EnabledAfter(targetSdkVersion = UPSIDE_DOWN_CAKE)
@ChangeId public static final long TEST_COMPAT_ID_4 = 368132057L;
}
diff --git a/core/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp
index bb4084e8f39e..f64dec8eb215 100644
--- a/core/jni/android_hardware_OverlayProperties.cpp
+++ b/core/jni/android_hardware_OverlayProperties.cpp
@@ -106,7 +106,7 @@ static jobjectArray android_hardware_OverlayProperties_getLutProperties(JNIEnv*
jlong nativeObject) {
gui::OverlayProperties* overlayProperties =
reinterpret_cast<gui::OverlayProperties*>(nativeObject);
- if (overlayProperties->lutProperties.has_value()) {
+ if (!overlayProperties || !overlayProperties->lutProperties) {
return NULL;
}
auto& lutProperties = overlayProperties->lutProperties.value();
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 49191ee02ad6..7ef7829c6ba5 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -33,6 +33,7 @@
#include <algorithm>
#include <array>
+#include <cctype>
#include <cstring>
#include <limits>
#include <memory>
@@ -1008,6 +1009,8 @@ jboolean android_os_Process_parseProcLineArray(JNIEnv* env, jobject clazz,
}
}
if ((mode&PROC_OUT_STRING) != 0 && di < NS) {
+ std::replace_if(buffer+start, buffer+end,
+ [](unsigned char c){ return !std::isprint(c); }, '?');
jstring str = env->NewStringUTF(buffer+start);
env->SetObjectArrayElement(outStrings, di, str);
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 82b463ec9091..1925b3a41b5f 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -758,54 +758,64 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
- ScopedIntArrayRW joffsets(env, joffsetArray);
- if (joffsets.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from joffsetArray");
- return;
- }
- ScopedIntArrayRW jdimensions(env, jdimensionArray);
- if (jdimensions.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jdimensionArray");
- return;
- }
- ScopedIntArrayRW jsizes(env, jsizeArray);
- if (jsizes.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsizeArray");
- return;
- }
- ScopedIntArrayRW jsamplingKeys(env, jsamplingKeyArray);
- if (jsamplingKeys.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsamplingKeyArray");
- return;
- }
+ std::vector<int32_t> offsets;
+ std::vector<int32_t> dimensions;
+ std::vector<int32_t> sizes;
+ std::vector<int32_t> samplingKeys;
+ int32_t fd = -1;
+
+ if (jdimensionArray) {
+ jsize numLuts = env->GetArrayLength(jdimensionArray);
+ ScopedIntArrayRW joffsets(env, joffsetArray);
+ if (joffsets.get() == nullptr) {
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from joffsetArray");
+ return;
+ }
+ ScopedIntArrayRW jdimensions(env, jdimensionArray);
+ if (jdimensions.get() == nullptr) {
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jdimensionArray");
+ return;
+ }
+ ScopedIntArrayRW jsizes(env, jsizeArray);
+ if (jsizes.get() == nullptr) {
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsizeArray");
+ return;
+ }
+ ScopedIntArrayRW jsamplingKeys(env, jsamplingKeyArray);
+ if (jsamplingKeys.get() == nullptr) {
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsamplingKeyArray");
+ return;
+ }
- jsize numLuts = env->GetArrayLength(jdimensionArray);
- std::vector<int32_t> offsets(joffsets.get(), joffsets.get() + numLuts);
- std::vector<int32_t> dimensions(jdimensions.get(), jdimensions.get() + numLuts);
- std::vector<int32_t> sizes(jsizes.get(), jsizes.get() + numLuts);
- std::vector<int32_t> samplingKeys(jsamplingKeys.get(), jsamplingKeys.get() + numLuts);
+ if (numLuts > 0) {
+ offsets = std::vector<int32_t>(joffsets.get(), joffsets.get() + numLuts);
+ dimensions = std::vector<int32_t>(jdimensions.get(), jdimensions.get() + numLuts);
+ sizes = std::vector<int32_t>(jsizes.get(), jsizes.get() + numLuts);
+ samplingKeys = std::vector<int32_t>(jsamplingKeys.get(), jsamplingKeys.get() + numLuts);
- ScopedFloatArrayRW jbuffers(env, jbufferArray);
- if (jbuffers.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRW from jbufferArray");
- return;
- }
+ ScopedFloatArrayRW jbuffers(env, jbufferArray);
+ if (jbuffers.get() == nullptr) {
+ jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRW from jbufferArray");
+ return;
+ }
- // create the shared memory and copy jbuffers
- size_t bufferSize = jbuffers.size() * sizeof(float);
- int32_t fd = ashmem_create_region("lut_shread_mem", bufferSize);
- if (fd < 0) {
- jniThrowRuntimeException(env, "ashmem_create_region() failed");
- return;
- }
- void* ptr = mmap(nullptr, bufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
- if (ptr == MAP_FAILED) {
- jniThrowRuntimeException(env, "Failed to map the shared memory");
- return;
+ // create the shared memory and copy jbuffers
+ size_t bufferSize = jbuffers.size() * sizeof(float);
+ fd = ashmem_create_region("lut_shared_mem", bufferSize);
+ if (fd < 0) {
+ jniThrowRuntimeException(env, "ashmem_create_region() failed");
+ return;
+ }
+ void* ptr = mmap(nullptr, bufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (ptr == MAP_FAILED) {
+ jniThrowRuntimeException(env, "Failed to map the shared memory");
+ return;
+ }
+ memcpy(ptr, jbuffers.get(), bufferSize);
+ // unmap
+ munmap(ptr, bufferSize);
+ }
}
- memcpy(ptr, jbuffers.get(), bufferSize);
- // unmap
- munmap(ptr, bufferSize);
transaction->setLuts(ctrl, base::unique_fd(fd), offsets, dimensions, sizes, samplingKeys);
}
@@ -1332,8 +1342,9 @@ static void nativeSetDisplaySize(JNIEnv* env, jclass clazz,
}
}
-static jobject convertDeviceProductInfoToJavaObject(
- JNIEnv* env, const std::optional<DeviceProductInfo>& info) {
+static jobject convertDeviceProductInfoToJavaObject(JNIEnv* env,
+ const std::optional<DeviceProductInfo>& info,
+ bool isInternal) {
using ModelYear = android::DeviceProductInfo::ModelYear;
using ManufactureYear = android::DeviceProductInfo::ManufactureYear;
using ManufactureWeekAndYear = android::DeviceProductInfo::ManufactureWeekAndYear;
@@ -1368,7 +1379,8 @@ static jobject convertDeviceProductInfoToJavaObject(
// Section 8.7 - Physical Address of HDMI Specification Version 1.3a
using android::hardware::display::IDeviceProductInfoConstants;
if (info->relativeAddress.size() != 4) {
- connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_UNKNOWN;
+ connectionToSinkType = isInternal ? IDeviceProductInfoConstants::CONNECTION_TO_SINK_BUILT_IN
+ : IDeviceProductInfoConstants::CONNECTION_TO_SINK_UNKNOWN;
} else if (info->relativeAddress[0] == 0) {
connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_BUILT_IN;
} else if (info->relativeAddress[1] == 0) {
@@ -1390,12 +1402,14 @@ static jobject nativeGetStaticDisplayInfo(JNIEnv* env, jclass clazz, jlong id) {
jobject object =
env->NewObject(gStaticDisplayInfoClassInfo.clazz, gStaticDisplayInfoClassInfo.ctor);
- env->SetBooleanField(object, gStaticDisplayInfoClassInfo.isInternal,
- info.connectionType == ui::DisplayConnectionType::Internal);
+
+ const bool isInternal = info.connectionType == ui::DisplayConnectionType::Internal;
+ env->SetBooleanField(object, gStaticDisplayInfoClassInfo.isInternal, isInternal);
env->SetFloatField(object, gStaticDisplayInfoClassInfo.density, info.density);
env->SetBooleanField(object, gStaticDisplayInfoClassInfo.secure, info.secure);
env->SetObjectField(object, gStaticDisplayInfoClassInfo.deviceProductInfo,
- convertDeviceProductInfoToJavaObject(env, info.deviceProductInfo));
+ convertDeviceProductInfoToJavaObject(env, info.deviceProductInfo,
+ isInternal));
env->SetIntField(object, gStaticDisplayInfoClassInfo.installOrientation,
static_cast<uint32_t>(info.installOrientation));
return object;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3e0c1200749e..00424590b2c8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1080,6 +1080,52 @@
<permission android:name="android.permission.SATELLITE_COMMUNICATION"
android:protectionLevel="role|signature|privileged" />
+ <!-- ================================== -->
+ <!-- Permissions associated with picture and sound profiles -->
+ <!-- ================================== -->
+ <eat-comment />
+
+ <!-- @FlaggedApi(android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES)
+ Allows an app to apply a {@link MediaQualityManager.PictureProfile} to a layer via
+ {@link MediaCodec.PARAMETER_KEY_PICTURE_PROFILE} and, additionally, system apps via
+ {@link SurfaceControl.Transaction#setPictureProfileHandle}.
+ -->
+ <permission android:name="android.permission.APPLY_PICTURE_PROFILE"
+ android:protectionLevel="normal"
+ android:featureFlag="android.media.tv.flags.apply_picture_profiles"/>
+
+ <!-- @hide
+ Allows MediaQualityService to observe any {@link MediaQualityManager.PictureProfile}
+ applied to any layer in the system by apps via
+ {@link MediaCodec.PARAMETER_KEY_PICTURE_PROFILE} and by system apps via
+ {@link SurfaceControl.Transaction#setPictureProfileHandle}.
+ -->
+ <permission android:name="android.permission.OBSERVE_PICTURE_PROFILES"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.media.tv.flags.apply_picture_profiles"/>
+
+ <!--
+ @SystemApi
+ @FlaggedApi("android.media.tv.flags.media_quality_fw")
+ Allows an application to access its picture profile from the media quality database.
+ <p> Protection level: signature|privileged|vendor privileged
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"
+ android:protectionLevel="signature|privileged|vendorPrivileged"
+ android:featureFlag="android.media.tv.flags.media_quality_fw"/>
+
+ <!--
+ @SystemApi
+ @FlaggedApi("android.media.tv.flags.media_quality_fw")
+ Allows an application to access its sound profile from the media quality database.
+ <p> Protection level: signature|privileged|vendor privileged
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"
+ android:protectionLevel="signature|privileged|vendorPrivileged"
+ android:featureFlag="android.media.tv.flags.media_quality_fw"/>
+
<!-- ====================================================================== -->
<!-- Permissions for accessing external storage -->
<!-- ====================================================================== -->
@@ -4144,6 +4190,37 @@
<uses-permission android:name="android.permission.QUERY_ADVANCED_PROTECTION_MODE"
android:featureFlag="android.security.aapm_api"/>
+ <!-- Allows an application to read the state of the ForensicService
+ @FlaggedApi(android.security.Flags.FLAG_AFL_API)
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.READ_FORENSIC_STATE"
+ android:featureFlag="android.security.afl_api"
+ android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.READ_FORENSIC_STATE"
+ android:featureFlag="android.security.afl_api"/>
+
+ <!-- Allows an application to change the state of the ForensicService
+ @FlaggedApi(android.security.Flags.FLAG_AFL_API)
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.MANAGE_FORENSIC_STATE"
+ android:featureFlag="android.security.afl_api"
+ android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.MANAGE_FORENSIC_STATE"
+ android:featureFlag="android.security.afl_api"/>
+
+ <!-- Must be required by any ForensicEventTransportService to ensure that
+ only the system can bind to it.
+ @FlaggedApi(android.security.Flags.FLAG_AFL_API)
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.BIND_FORENSIC_EVENT_TRANSPORT_SERVICE"
+ android:featureFlag="android.security.afl_api"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.BIND_FORENSIC_EVENT_TRANSPORT_SERVICE"
+ android:featureFlag="android.security.afl_api"/>
+
<!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.-->
<permission android:name="android.permission.PROVISION_DEMO_DEVICE"
android:protectionLevel="signature|setup|knownSigner"
@@ -4991,16 +5068,16 @@
android:protectionLevel="signature|privileged|role"
android:featureFlag="com.android.settingslib.flags.settings_catalyst" />
- <!-- @FlaggedApi(com.android.settingslib.flags.Flags.FLAG_SETTINGS_CATALYST)
+ <!-- @FlaggedApi(com.android.settingslib.flags.Flags.FLAG_WRITE_SYSTEM_PREFERENCE_PERMISSION_ENABLED)
Allows an application to access the Settings Preference services to write settings
values exposed by the system Settings app and system apps that contribute settings surfaced
in the Settings app.
<p>This allows the calling application to write settings values
through the host application, agnostic of underlying storage.
- <p>Protection Level: signature|privileged|appop - appop to be added in followup -->
+ <p>Protection Level: signature|privileged|appop -->
<permission android:name="android.permission.WRITE_SYSTEM_PREFERENCES"
- android:protectionLevel="signature|privileged"
- android:featureFlag="com.android.settingslib.flags.settings_catalyst" />
+ android:protectionLevel="signature|privileged|appop"
+ android:featureFlag="com.android.settingslib.flags.write_system_preference_permission_enabled" />
<!-- ========================================= -->
<!-- Permissions for special development tools -->
@@ -7844,7 +7921,31 @@
<!-- @SystemApi Allows an application to access shared libraries.
@hide -->
<permission android:name="android.permission.ACCESS_SHARED_LIBRARIES"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer"
+ android:featureFlag="!android.content.pm.sdk_dependency_installer" />
+
+ <!-- @SystemApi Allows an application to access shared libraries.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_SHARED_LIBRARIES"
+ android:protectionLevel="signature|installer|role"
+ android:featureFlag="android.content.pm.sdk_dependency_installer" />
+
+ <!-- @SystemApi Permission held by the system to allow binding to the dependency installer role
+ holder.
+ @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ @hide -->
+ <permission android:name="android.permission.BIND_DEPENDENCY_INSTALLER"
+ android:protectionLevel="signature"
+ android:featureFlag="android.content.pm.sdk_dependency_installer" />
+
+ <!-- @SystemApi Allows an application to install shared libraries of types
+ {@link android.content.pm.SharedLibraryInfo#TYPE_STATIC} or
+ {@link android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE}.
+ @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ @hide -->
+ <permission android:name="android.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES"
+ android:protectionLevel="signature|role"
+ android:featureFlag="android.content.pm.sdk_dependency_installer" />
<!-- Allows an app to log compat change usage.
@hide <p>Not for use by third-party applications.</p> -->
@@ -8565,27 +8666,6 @@
<permission android:name="android.permission.RESERVED_FOR_TESTING_SIGNATURE"
android:protectionLevel="signature"/>
- <!--
- @SystemApi
- @FlaggedApi("android.media.tv.flags.media_quality_fw")
- Allows an application to access its picture profile from the media quality database.
- <p> Protection level: signature|privileged|vendor privileged
- @hide
- -->
- <permission android:name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"
- android:protectionLevel="signature|privileged|vendorPrivileged"
- android:featureFlag="android.media.tv.flags.media_quality_fw"/>
-
- <!--
- @SystemApi
- @FlaggedApi("android.media.tv.flags.media_quality_fw")
- Allows an application to access its sound profile from the media quality database.
- <p> Protection level: signature|privileged|vendor privileged
- @hide
- -->
- <permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"
- android:protectionLevel="signature|privileged|vendorPrivileged"
- android:featureFlag="android.media.tv.flags.media_quality_fw"/>
<!-- @SystemApi
@FlaggedApi("android.content.pm.verification_service")
Allows app to be the verification agent to verify packages.
diff --git a/core/res/res/layout/list_content_simple.xml b/core/res/res/layout/list_content_simple.xml
index 6f9f1e0f0f6f..961668e6bf2e 100644
--- a/core/res/res/layout/list_content_simple.xml
+++ b/core/res/res/layout/list_content_simple.xml
@@ -20,5 +20,6 @@
<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:drawSelectorOnTop="false"
/>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e6dedce8feaf..72467b367cd5 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -10285,22 +10285,25 @@
</declare-styleable>
<!-- @hide -->
+ <attr name="modifierState">
+ <flag name="META" value="0x10000" />
+ <flag name="CTRL" value="0x1000" />
+ <flag name="ALT" value="0x02" />
+ <flag name="SHIFT" value="0x1" />
+ <flag name="SYM" value="0x4" />
+ <flag name="FUNCTION" value="0x8" />
+ <flag name="CAPS_LOCK" value="0x100000" />
+ <flag name="NUM_LOCK" value="0x200000" />
+ <flag name="SCROLL_LOCK" value="0x400000" />
+ </attr>
+
+ <!-- @hide -->
<declare-styleable name="HardwareDefinedShortcut">
<attr name="keycode" />
<!-- The values are taken from public constants for modifier state defined in
{@see KeyEvent.java}. Here we allow multiple modifier flags as value, since this
represents the modifier state -->
- <attr name="modifierState">
- <flag name="META" value="0x10000" />
- <flag name="CTRL" value="0x1000" />
- <flag name="ALT" value="0x02" />
- <flag name="SHIFT" value="0x1" />
- <flag name="SYM" value="0x4" />
- <flag name="FUNCTION" value="0x8" />
- <flag name="CAPS_LOCK" value="0x100000" />
- <flag name="NUM_LOCK" value="0x200000" />
- <flag name="SCROLL_LOCK" value="0x400000" />
- </attr>
+ <attr name="modifierState" />
<attr name="outKeycode" />
</declare-styleable>
@@ -10309,6 +10312,11 @@
<attr name="keycode" />
</declare-styleable>
+ <declare-styleable name="Bookmark">
+ <attr name="keycode" />
+ <attr name="modifierState" />
+ </declare-styleable>
+
<declare-styleable name="MediaRouteButton">
<!-- This drawable is a state list where the "activated" state
indicates active media routing. Non-activated indicates
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 41dec3776b5c..7ef539492aa4 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1855,13 +1855,23 @@
{@link android.R.styleable#AndroidManifestProcess process} tag, or to an
{@link android.R.styleable#AndroidManifestApplication application} tag (to supply
a default setting for all application components). -->
- <attr name="memtagMode">
+ <attr name="memtagMode">
<enum name="default" value="-1" />
<enum name="off" value="0" />
<enum name="async" value="1" />
<enum name="sync" value="2" />
</attr>
+ <!-- This attribute will be used to override app compatibility mode on 16 KB devices.
+ If set to enabled, Natives lib will be extracted from APK if they are not page aligned on
+ 16 KB device. 4 KB natives libs will be loaded app-compat mode if they are eligible.
+ @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) -->
+ <attr name="pageSizeCompat">
+ <enum name="enabled" value="5" />
+ <enum name="disabled" value="6" />
+ </attr>
+
+
<!-- Attribution tag to be used for permission sub-attribution if a
permission is checked in {@link android.content.Context#sendBroadcast(Intent, String)}.
Multiple tags can be specified separated by '|'.
@@ -2212,6 +2222,9 @@
<attr name="memtagMode" />
+ <!-- @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) -->
+ <attr name="pageSizeCompat" />
+
<!-- If {@code true} enables automatic zero initialization of all native heap
allocations. -->
<attr name="nativeHeapZeroInitialized" format="boolean" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 969ee2e16deb..5088b5af4725 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4205,9 +4205,17 @@
must match the value of config_cameraLaunchGestureSensorType in OEM's HAL -->
<string translatable="false" name="config_cameraLaunchGestureSensorStringType"></string>
+ <!-- Allow the gesture to double tap the power button to trigger a target action. -->
+ <bool name="config_doubleTapPowerGestureEnabled">true</bool>
<!-- Allow the gesture to double tap the power button twice to start the camera while the device
is non-interactive. -->
<bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool>
+ <!-- Allow the gesture to double tap the power button twice to launch the wallet. -->
+ <bool name="config_walletDoubleTapPowerGestureEnabled">true</bool>
+ <!-- Default target action for double tap of the power button gesture.
+ 0: Launch camera
+ 1: Launch wallet -->
+ <integer name="config_defaultDoubleTapPowerGestureAction">0</integer>
<!-- Allow the gesture to quick tap the power button multiple times to start the emergency sos
experience while the device is non-interactive. -->
@@ -6567,7 +6575,7 @@
</string-array>
<!-- the number of the max cached processes in the system. -->
- <integer name="config_customizedMaxCachedProcesses">32</integer>
+ <integer name="config_customizedMaxCachedProcesses">1024</integer>
<!-- Whether this device should support taking app snapshots on closure -->
<bool name="config_disableTaskSnapshots">false</bool>
@@ -7212,9 +7220,12 @@
<!-- Package for opening identity check settings page [CHAR LIMIT=NONE] [DO NOT TRANSLATE] -->
<string name="identity_check_settings_package_name">com\u002eandroid\u002esettings</string>
- <!-- The name of the service for forensic backup transport. -->
- <string name="config_forensicBackupTransport" translatable="false"></string>
+ <!-- The name of the service for forensic event transport. -->
+ <string name="config_forensicEventTransport" translatable="false"></string>
<!-- Whether to enable fp unlock when screen turns off on udfps devices -->
<bool name="config_screen_off_udfps_enabled">false</bool>
+
+ <!-- The name of the system package that will hold the dependency installer role. -->
+ <string name="config_systemDependencyInstaller" translatable="false" />
</resources>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 31e9913dd988..4ec27a31df8c 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -318,6 +318,12 @@
<bool name="config_oem_enabled_satellite_access_allow">true</bool>
<java-symbol type="bool" name="config_oem_enabled_satellite_access_allow" />
+ <!-- Whether the satellite modem support concurrent TN scanning while device is in
+ NTN mode.
+ -->
+ <bool name="config_satellite_modem_support_concurrent_tn_scanning">true</bool>
+ <java-symbol type="bool" name="config_satellite_modem_support_concurrent_tn_scanning" />
+
<!-- The time duration in seconds which is used to decide whether the Location returned from
LocationManager#getLastKnownLocation is fresh.
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index b6436d0b30a5..ce46c649148c 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -131,6 +131,8 @@
<public name="alternateLauncherIcons"/>
<!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) -->
<public name="alternateLauncherLabels"/>
+ <!-- @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) -->
+ <public name="pageSizeCompat" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01b60000">
@@ -140,6 +142,9 @@
</staging-public-group>
<staging-public-group type="string" first-id="0x01b40000">
+ <!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ @hide @SystemApi -->
+ <public name="config_systemDependencyInstaller" />
</staging-public-group>
<staging-public-group type="dimen" first-id="0x01b30000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9dd302784c2c..0e120758a144 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3140,9 +3140,12 @@
<!-- Gesture -->
<java-symbol type="integer" name="config_cameraLaunchGestureSensorType" />
<java-symbol type="string" name="config_cameraLaunchGestureSensorStringType" />
- <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
<java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
<java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
+ <java-symbol type="bool" name="config_doubleTapPowerGestureEnabled" />
+ <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
+ <java-symbol type="bool" name="config_walletDoubleTapPowerGestureEnabled" />
+ <java-symbol type="integer" name="config_defaultDoubleTapPowerGestureAction" />
<java-symbol type="bool" name="config_emergencyGestureEnabled" />
<java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" />
<java-symbol type="bool" name="config_defaultEmergencyGestureSoundEnabled" />
@@ -5679,8 +5682,8 @@
<java-symbol type="string" name="identity_check_settings_action" />
<java-symbol type="string" name="identity_check_settings_package_name" />
- <!-- Forensic backup transport -->
- <java-symbol type="string" name="config_forensicBackupTransport" />
+ <!-- Forensic event transport -->
+ <java-symbol type="string" name="config_forensicEventTransport" />
<!-- Fingerprint screen off unlock config -->
<java-symbol type="bool" name="config_screen_off_udfps_enabled" />
diff --git a/core/res/res/xml/bookmarks.xml b/core/res/res/xml/bookmarks.xml
index 22d02262c388..e735784ee5bb 100644
--- a/core/res/res/xml/bookmarks.xml
+++ b/core/res/res/xml/bookmarks.xml
@@ -31,29 +31,37 @@
'u': Calculator
'y': YouTube
-->
-<bookmarks>
+<bookmarks xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<bookmark
role="android.app.role.BROWSER"
- shortcut="b" />
+ androidprv:keycode="KEYCODE_B"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CONTACTS"
- shortcut="c" />
+ androidprv:keycode="KEYCODE_C"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_EMAIL"
- shortcut="e" />
+ androidprv:keycode="KEYCODE_E"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CALENDAR"
- shortcut="k" />
+ androidprv:keycode="KEYCODE_K"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_MAPS"
- shortcut="m" />
+ androidprv:keycode="KEYCODE_M"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_MUSIC"
- shortcut="p" />
+ androidprv:keycode="KEYCODE_P"
+ androidprv:modifierState="META" />
<bookmark
role="android.app.role.SMS"
- shortcut="s" />
+ androidprv:keycode="KEYCODE_S"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CALCULATOR"
- shortcut="u" />
+ androidprv:keycode="KEYCODE_U"
+ androidprv:modifierState="META" />
</bookmarks>
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
index 9dd196daf412..f62d420510c3 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
@@ -507,7 +507,8 @@ public class EditorInfoTest {
+ "prefix: supportedHandwritingGestureTypes=(none)\n"
+ "prefix: supportedHandwritingGesturePreviewTypes=(none)\n"
+ "prefix: isStylusHandwritingEnabled=false\n"
- + "prefix: contentMimeTypes=null\n");
+ + "prefix: contentMimeTypes=null\n"
+ + "prefix: writingToolsEnabled=true\n");
}
@Test
@@ -539,6 +540,7 @@ public class EditorInfoTest {
info.hintLocales = LocaleList.forLanguageTags("en,es,zh");
info.contentMimeTypes = new String[] {"image/png"};
info.targetInputMethodUser = UserHandle.of(10);
+ info.setWritingToolsEnabled(false);
final StringBuilder sb = new StringBuilder();
info.dump(new StringBuilderPrinter(sb), "prefix2: ");
assertThat(sb.toString()).isEqualTo(
@@ -555,7 +557,8 @@ public class EditorInfoTest {
+ "prefix2: supportedHandwritingGesturePreviewTypes=SELECT\n"
+ "prefix2: isStylusHandwritingEnabled=" + isStylusHandwritingEnabled + "\n"
+ "prefix2: contentMimeTypes=[image/png]\n"
- + "prefix2: targetInputMethodUserId=10\n");
+ + "prefix2: targetInputMethodUserId=10\n"
+ + "prefix2: writingToolsEnabled=false\n");
}
@Test
@@ -576,7 +579,8 @@ public class EditorInfoTest {
+ "prefix: supportedHandwritingGestureTypes=(none)\n"
+ "prefix: supportedHandwritingGesturePreviewTypes=(none)\n"
+ "prefix: isStylusHandwritingEnabled=false\n"
- + "prefix: contentMimeTypes=null\n");
+ + "prefix: contentMimeTypes=null\n"
+ + "prefix: writingToolsEnabled=true\n");
}
@Test
@@ -621,4 +625,9 @@ public class EditorInfoTest {
infoCopy.extras.putString("testKey2", "testValue");
assertFalse(TEST_EDITOR_INFO.kindofEquals(infoCopy));
}
+
+ @Test
+ public void testWritingToolsEnabledbyDefault() {
+ assertTrue(TEST_EDITOR_INFO.isWritingToolsEnabled());
+ }
}
diff --git a/core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java b/core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java
new file mode 100644
index 000000000000..df5cd4e95418
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+@Presubmit
+@RunWith(JUnit4.class)
+public final class SharedLibraryInfoTest {
+
+ @Rule
+ public final CheckFlagsRule checkFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private static final String LIBRARY_NAME = "name";
+ private static final long VERSION_MAJOR = 1L;
+ private static final List<String> CERT_DIGESTS = ImmutableList.of("digest1", "digest2");
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ public void sharedLibraryInfo_serializedAndDeserialized_retainsCertDigestInfo() {
+ SharedLibraryInfo toParcel = new SharedLibraryInfo(LIBRARY_NAME, VERSION_MAJOR,
+ SharedLibraryInfo.TYPE_SDK_PACKAGE, CERT_DIGESTS);
+
+ SharedLibraryInfo fromParcel = parcelAndUnparcel(toParcel);
+
+ assertThat(fromParcel.getCertDigests().size()).isEqualTo(toParcel.getCertDigests().size());
+ assertThat(fromParcel.getCertDigests().get(0)).isEqualTo(toParcel.getCertDigests().get(0));
+ assertThat(fromParcel.getCertDigests().get(1)).isEqualTo(toParcel.getCertDigests().get(1));
+ }
+
+ private SharedLibraryInfo parcelAndUnparcel(SharedLibraryInfo sharedLibraryInfo) {
+ Parcel parcel = Parcel.obtain();
+ parcel.setDataPosition(0);
+ sharedLibraryInfo.writeToParcel(parcel, /* flags= */0);
+
+ parcel.setDataPosition(0);
+ return SharedLibraryInfo.CREATOR.createFromParcel(parcel);
+ }
+}
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index 6149382d0800..4620cb8d8148 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -12,3 +12,6 @@ per-file PerformanceHintManagerTest.java = file:/ADPF_OWNERS
# Caching
per-file IpcDataCache* = file:/PERFORMANCE_OWNERS
+
+# RemoteCallbackList
+per-file RemoteCallbackListTest.java = shayba@google.com
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 541ca602a386..fea7cb4b422c 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -93,6 +93,10 @@ applications that come with the platform
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
</privapp-permissions>
+ <privapp-permissions package="com.android.media.quality">
+ <permission name="android.permission.OBSERVE_PICTURE_PROFILES"/>
+ </privapp-permissions>
+
<privapp-permissions package="com.android.mtp">
<permission name="android.permission.ACCESS_MTP"/>
<permission name="android.permission.MANAGE_USB"/>
@@ -262,6 +266,8 @@ applications that come with the platform
<!-- BLUETOOTH_PRIVILEGED is needed for test only -->
<permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
<permission name="android.permission.BIND_APPWIDGET"/>
+ <!-- Needed for CTS tests only -->
+ <permission name="android.permission.OBSERVE_PICTURE_PROFILES"/>
<permission name="android.permission.CHANGE_APP_IDLE_STATE"/>
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<permission name="android.permission.CHANGE_CONFIGURATION"/>
@@ -597,6 +603,9 @@ applications that come with the platform
<!-- Permissions required for CTS test - SettingsPreferenceServiceClientTest -->
<permission name="android.permission.READ_SYSTEM_PREFERENCES" />
<permission name="android.permission.WRITE_SYSTEM_PREFERENCES" />
+ <!-- Permission required for CTS test - ForensicManagerTest -->
+ <permission name="android.permission.READ_FORENSIC_STATE" />
+ <permission name="android.permission.MANAGE_FORENSIC_STATE" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/BlendMode.java b/graphics/java/android/graphics/BlendMode.java
index 5c294aa72ce8..c6ae680d01bf 100644
--- a/graphics/java/android/graphics/BlendMode.java
+++ b/graphics/java/android/graphics/BlendMode.java
@@ -571,10 +571,10 @@ public enum BlendMode {
}
@NonNull
- private final Xfermode mXfermode;
+ private final PorterDuffXfermode mXfermode;
BlendMode(int mode) {
- mXfermode = new Xfermode();
+ mXfermode = new PorterDuffXfermode();
mXfermode.porterDuffMode = mode;
}
@@ -582,7 +582,7 @@ public enum BlendMode {
* @hide
*/
@NonNull
- public Xfermode getXfermode() {
+ public PorterDuffXfermode getXfermode() {
return mXfermode;
}
}
diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java
index 977aeaa2f4d9..e7145686247e 100644
--- a/graphics/java/android/graphics/ComposeShader.java
+++ b/graphics/java/android/graphics/ComposeShader.java
@@ -40,9 +40,12 @@ public class ComposeShader extends Shader {
* @param mode The mode that combines the colors from the two shaders. If mode
* is null, then SRC_OVER is assumed.
*/
+ //TODO(358126864): allow a ComposeShader to accept a RuntimeXfermode
@Deprecated
public ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, @NonNull Xfermode mode) {
- this(shaderA, shaderB, mode.porterDuffMode);
+ this(shaderA, shaderB,
+ mode instanceof PorterDuffXfermode ? ((PorterDuffXfermode) mode).porterDuffMode
+ : BlendMode.SRC_OVER.getXfermode().porterDuffMode);
}
/**
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 56bb0f0d12d5..9bf4d65e1865 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -71,6 +71,7 @@ public class Paint {
private long mNativePaint;
private long mNativeShader;
private long mNativeColorFilter;
+ private long mNativeXfermode;
// Use a Holder to allow static initialization of Paint in the boot image.
private static class NoImagePreloadHolder {
@@ -735,6 +736,7 @@ public class Paint {
mPathEffect = null;
mShader = null;
mNativeShader = 0;
+ mNativeXfermode = 0;
mTypeface = null;
mXfermode = null;
@@ -780,6 +782,7 @@ public class Paint {
mNativeShader = paint.mNativeShader;
mTypeface = paint.mTypeface;
mXfermode = paint.mXfermode;
+ mNativeXfermode = paint.mNativeXfermode;
mHasCompatScaling = paint.mHasCompatScaling;
mCompatScaling = paint.mCompatScaling;
@@ -815,7 +818,7 @@ public class Paint {
*
* Note: Although this method is |synchronized|, this is simply so it
* is not thread-hostile to multiple threads calling this method. It
- * is still unsafe to attempt to change the Shader/ColorFilter while
+ * is still unsafe to attempt to change the Shader/ColorFilter/Xfermode while
* another thread attempts to access the native object.
*
* @hide
@@ -833,6 +836,15 @@ public class Paint {
mNativeColorFilter = newNativeColorFilter;
nSetColorFilter(mNativePaint, mNativeColorFilter);
}
+ if (com.android.graphics.hwui.flags.Flags.runtimeColorFiltersBlenders()) {
+ if (mXfermode instanceof RuntimeXfermode) {
+ long newNativeXfermode = ((RuntimeXfermode) mXfermode).createNativeInstance();
+ if (newNativeXfermode != mNativeXfermode) {
+ mNativeXfermode = newNativeXfermode;
+ nSetXfermode(mNativePaint, mNativeXfermode);
+ }
+ }
+ }
return mNativePaint;
}
@@ -1427,16 +1439,17 @@ public class Paint {
}
/**
- * Get the paint's blend mode object.
+ * Get the paint's blend mode object. Will return null if there is a Xfermode applied that
+ * cannot be represented by a blend mode (i.e. a custom {@code RuntimeXfermode}
*
* @return the paint's blend mode (or null)
*/
@Nullable
public BlendMode getBlendMode() {
- if (mXfermode == null) {
+ if (mXfermode == null || !(mXfermode instanceof PorterDuffXfermode)) {
return null;
} else {
- return BlendMode.fromValue(mXfermode.porterDuffMode);
+ return BlendMode.fromValue(((PorterDuffXfermode) mXfermode).porterDuffMode);
}
}
@@ -1459,8 +1472,17 @@ public class Paint {
@Nullable
private Xfermode installXfermode(Xfermode xfermode) {
- int newMode = xfermode != null ? xfermode.porterDuffMode : Xfermode.DEFAULT;
- int curMode = mXfermode != null ? mXfermode.porterDuffMode : Xfermode.DEFAULT;
+ if (com.android.graphics.hwui.flags.Flags.runtimeColorFiltersBlenders()) {
+ if (xfermode instanceof RuntimeXfermode) {
+ mXfermode = xfermode;
+ nSetXfermode(mNativePaint, ((RuntimeXfermode) xfermode).createNativeInstance());
+ return xfermode;
+ }
+ }
+ int newMode = (xfermode instanceof PorterDuffXfermode)
+ ? ((PorterDuffXfermode) xfermode).porterDuffMode : PorterDuffXfermode.DEFAULT;
+ int curMode = (mXfermode instanceof PorterDuffXfermode)
+ ? ((PorterDuffXfermode) mXfermode).porterDuffMode : PorterDuffXfermode.DEFAULT;
if (newMode != curMode) {
nSetXfermode(mNativePaint, newMode);
}
@@ -3823,6 +3845,8 @@ public class Paint {
@CriticalNative
private static native void nSetXfermode(long paintPtr, int xfermode);
@CriticalNative
+ private static native void nSetXfermode(long paintPtr, long xfermodePtr);
+ @CriticalNative
private static native long nSetPathEffect(long paintPtr, long effect);
@CriticalNative
private static native long nSetMaskFilter(long paintPtr, long maskfilter);
diff --git a/graphics/java/android/graphics/PorterDuffXfermode.java b/graphics/java/android/graphics/PorterDuffXfermode.java
index ff9ff8b0069d..83d0507a5074 100644
--- a/graphics/java/android/graphics/PorterDuffXfermode.java
+++ b/graphics/java/android/graphics/PorterDuffXfermode.java
@@ -29,6 +29,9 @@ public class PorterDuffXfermode extends Xfermode {
*
* @param mode The porter-duff mode that is applied
*/
+ static final int DEFAULT = PorterDuff.Mode.SRC_OVER.nativeInt;
+ int porterDuffMode = DEFAULT;
+ PorterDuffXfermode() {}
public PorterDuffXfermode(PorterDuff.Mode mode) {
porterDuffMode = mode.nativeInt;
}
diff --git a/graphics/java/android/graphics/RuntimeColorFilter.java b/graphics/java/android/graphics/RuntimeColorFilter.java
index 52724ceaf301..d112f7153fca 100644
--- a/graphics/java/android/graphics/RuntimeColorFilter.java
+++ b/graphics/java/android/graphics/RuntimeColorFilter.java
@@ -283,6 +283,23 @@ public class RuntimeColorFilter extends ColorFilter {
nativeUpdateChild(getNativeInstance(), filterName, colorFilter.getNativeInstance());
}
+ /**
+ * Assigns the uniform xfermode to the provided xfermode parameter. If the shader program does
+ * not have a uniform xfermode with that name then an IllegalArgumentException is thrown.
+ *
+ * @param xfermodeName name matching the uniform declared in the AGSL program
+ * @param xfermode filter passed into the AGSL program for sampling
+ */
+ public void setInputXfermode(@NonNull String xfermodeName, @NonNull RuntimeXfermode xfermode) {
+ if (xfermodeName == null) {
+ throw new NullPointerException("The xfermodeName parameter must not be null");
+ }
+ if (xfermode == null) {
+ throw new NullPointerException("The xfermode parameter must not be null");
+ }
+ nativeUpdateChild(getNativeInstance(), xfermodeName, xfermode.createNativeInstance());
+ }
+
/** @hide */
@Override
protected long createNativeInstance() {
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index 78d257f86613..6316c1fb8b47 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -18,10 +18,13 @@ package android.graphics;
import android.annotation.ColorInt;
import android.annotation.ColorLong;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.util.ArrayMap;
import android.view.Window;
+import com.android.graphics.hwui.flags.Flags;
+
import libcore.util.NativeAllocationRegistry;
/**
@@ -525,6 +528,45 @@ public class RuntimeShader extends Shader {
discardNativeInstance();
}
+ /**
+ * Assigns the uniform color filter to the provided color filter parameter. If the shader
+ * program does not have a uniform color filter with that name then an IllegalArgumentException
+ * is thrown.
+ *
+ * @param filterName name matching the uniform declared in the AGSL program
+ * @param colorFilter filter passed into the AGSL program for sampling
+ */
+ @FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS)
+ public void setInputColorFilter(@NonNull String filterName, @NonNull ColorFilter colorFilter) {
+ if (filterName == null) {
+ throw new NullPointerException("The filterName parameter must not be null");
+ }
+ if (colorFilter == null) {
+ throw new NullPointerException("The colorFilter parameter must not be null");
+ }
+ nativeUpdateChild(mNativeInstanceRuntimeShaderBuilder, filterName,
+ colorFilter.getNativeInstance());
+ }
+
+ /**
+ * Assigns the uniform xfermode to the provided xfermode parameter. If the shader program does
+ * not have a uniform xfermode with that name then an IllegalArgumentException is thrown.
+ *
+ * @param xfermodeName name matching the uniform declared in the AGSL program
+ * @param xfermode filter passed into the AGSL program for sampling
+ */
+ @FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS)
+ public void setInputXfermode(@NonNull String xfermodeName, @NonNull RuntimeXfermode xfermode) {
+ if (xfermodeName == null) {
+ throw new NullPointerException("The xfermodeName parameter must not be null");
+ }
+ if (xfermode == null) {
+ throw new NullPointerException("The xfermode parameter must not be null");
+ }
+ nativeUpdateChild(mNativeInstanceRuntimeShaderBuilder, xfermodeName,
+ xfermode.createNativeInstance());
+ }
+
/** @hide */
@Override
@@ -552,5 +594,7 @@ public class RuntimeShader extends Shader {
int value4, int count);
private static native void nativeUpdateShader(
long shaderBuilder, String shaderName, long shader);
+ private static native void nativeUpdateChild(
+ long shaderBuilder, String childName, long child);
}
diff --git a/graphics/java/android/graphics/RuntimeXfermode.java b/graphics/java/android/graphics/RuntimeXfermode.java
new file mode 100644
index 000000000000..51d97a4b7487
--- /dev/null
+++ b/graphics/java/android/graphics/RuntimeXfermode.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.ColorInt;
+import android.annotation.ColorLong;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.graphics.hwui.flags.Flags;
+
+import libcore.util.NativeAllocationRegistry;
+
+
+/**
+ * <p>A {@link RuntimeXfermode} calculates a per-pixel color based on the output of a user
+ * * defined Android Graphics Shading Language (AGSL) function.</p>
+ *
+ * <p>This AGSL function takes in two input colors to be operated on. These colors are in sRGB
+ * * and the output is also interpreted as sRGB. The AGSL function signature expects a single input
+ * * of color (packed as a half4 or float4 or vec4).</p>
+ *
+ * <pre class="prettyprint">
+ * vec4 main(half4 src, half4 dst);
+ * </pre>
+ */
+@FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS)
+public class RuntimeXfermode extends Xfermode {
+
+ private static class NoImagePreloadHolder {
+ public static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ RuntimeXfermode.class.getClassLoader(), nativeGetFinalizer());
+ }
+
+ private long mBuilderNativeInstance;
+
+ /**
+ * Creates a new RuntimeBlender.
+ *
+ * @param agsl The text of AGSL color filter program to run.
+ */
+ public RuntimeXfermode(@NonNull String agsl) {
+ if (agsl == null) {
+ throw new NullPointerException("RuntimeShader requires a non-null AGSL string");
+ }
+ mBuilderNativeInstance = nativeCreateBlenderBuilder(agsl);
+ RuntimeXfermode.NoImagePreloadHolder.sRegistry.registerNativeAllocation(
+ this, mBuilderNativeInstance);
+ }
+ /**
+ * Sets the uniform color value corresponding to this color filter. If the effect does not have
+ * a uniform with that name or if the uniform is declared with a type other than vec3 or vec4
+ * and corresponding layout(color) annotation then an IllegalArgumentException is thrown.
+ *
+ * @param uniformName name matching the color uniform declared in the AGSL program
+ * @param color the provided sRGB color
+ */
+ public void setColorUniform(@NonNull String uniformName, @ColorInt int color) {
+ setUniform(uniformName, Color.valueOf(color).getComponents(), true);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to this color filter. If the effect does not have
+ * a uniform with that name or if the uniform is declared with a type other than vec3 or vec4
+ * and corresponding layout(color) annotation then an IllegalArgumentException is thrown.
+ *
+ * @param uniformName name matching the color uniform declared in the AGSL program
+ * @param color the provided sRGB color
+ */
+ public void setColorUniform(@NonNull String uniformName, @ColorLong long color) {
+ Color exSRGB = Color.valueOf(color).convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
+ setUniform(uniformName, exSRGB.getComponents(), true);
+ }
+
+ /**
+ * Sets the uniform color value corresponding to this color filter. If the effect does not have
+ * a uniform with that name or if the uniform is declared with a type other than vec3 or vec4
+ * and corresponding layout(color) annotation then an IllegalArgumentException is thrown.
+ *
+ * @param uniformName name matching the color uniform declared in the AGSL program
+ * @param color the provided sRGB color
+ */
+ public void setColorUniform(@NonNull String uniformName, @NonNull Color color) {
+ if (color == null) {
+ throw new NullPointerException("The color parameter must not be null");
+ }
+ Color exSRGB = color.convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
+ setUniform(uniformName, exSRGB.getComponents(), true);
+ }
+
+ /**
+ * Sets the uniform value corresponding to this color filter. If the effect does not have a
+ * uniform with that name or if the uniform is declared with a type other than a float or
+ * float[1] then an IllegalArgumentException is thrown.
+ *
+ * @param uniformName name matching the uniform declared in the AGSL program
+ */
+ public void setFloatUniform(@NonNull String uniformName, float value) {
+ setFloatUniform(uniformName, value, 0.0f, 0.0f, 0.0f, 1);
+ }
+
+ /**
+ * Sets the uniform value corresponding to this color filter. If the effect does not have a
+ * uniform with that name or if the uniform is declared with a type other than a vec2 or
+ * float[2] then an IllegalArgumentException is thrown.
+ *
+ * @param uniformName name matching the uniform declared in the AGSL program
+ */
+ public void setFloatUniform(@NonNull String uniformName, float value1, float value2) {
+ setFloatUniform(uniformName, value1, value2, 0.0f, 0.0f, 2);
+ }
+
+ /**
+ * Sets the uniform value corresponding to this color filter. If the effect does not have a
+ * uniform with that name or if the uniform is declared with a type other than a vec3 or
+ * float[3] then an IllegalArgumentException is thrown.
+ *
+ * @param uniformName name matching the uniform declared in the AGSL program
+ */
+ public void setFloatUniform(@NonNull String uniformName, float value1, float value2,
+ float value3) {
+ setFloatUniform(uniformName, value1, value2, value3, 0.0f, 3);
+
+ }
+
+ /**
+ * Sets the uniform value corresponding to this color filter. If the effect does not have a
+ * uniform with that name or if the uniform is declared with a type other than a vec4 or
+ * float[4] then an IllegalArgumentException is thrown.
+ *
+ * @param uniformName name matching the uniform declared in the AGSL program
+ */
+ public void setFloatUniform(@NonNull String uniformName, float value1, float value2,
+ float value3, float value4) {
+ setFloatUniform(uniformName, value1, value2, value3, value4, 4);
+ }
+
+ /**
+ * Sets the uniform value corresponding to this color filter. If the effect does not have a
+ * uniform with that name or if the uniform is declared with a type other than a float
+ * (for N=1), vecN, or float[N] where N is the length of the values param then an
+ * IllegalArgumentException is thrown.
+ *
+ * @param uniformName name matching the uniform declared in the AGSL program
+ */
+ public void setFloatUniform(@NonNull String uniformName, @NonNull float[] values) {
+ setUniform(uniformName, values, false);
+ }
+
+ private void setFloatUniform(@NonNull String uniformName, float value1, float value2,
+ float value3, float value4, int count) {
+ if (uniformName == null) {
+ throw new NullPointerException("The uniformName parameter must not be null");
+ }
+ nativeUpdateUniforms(mBuilderNativeInstance, uniformName, value1, value2, value3, value4,
+ count);
+ }
+
+ private void setUniform(@NonNull String uniformName, @NonNull float[] values, boolean isColor) {
+ if (uniformName == null) {
+ throw new NullPointerException("The uniformName parameter must not be null");
+ }
+ if (values == null) {
+ throw new NullPointerException("The uniform values parameter must not be null");
+ }
+ nativeUpdateUniforms(mBuilderNativeInstance, uniformName, values, isColor);
+ }
+
+ /**
+ * Sets the uniform value corresponding to this color filter. If the effect does not have a
+ * uniform with that name or if the uniform is declared with a type other than an int or int[1]
+ * then an IllegalArgumentException is thrown.
+ *
+ * @param uniformName name matching the uniform declared in the AGSL program
+ */
+ public void setIntUniform(@NonNull String uniformName, int value) {
+ setIntUniform(uniformName, value, 0, 0, 0, 1);
+ }
+
+ /**
+ * Sets the uniform value corresponding to this color filter. If the effect does not have a
+ * uniform with that name or if the uniform is declared with a type other than an ivec2 or
+ * int[2] then an IllegalArgumentException is thrown.
+ *
+ * @param uniformName name matching the uniform declared in the AGSL program
+ */
+ public void setIntUniform(@NonNull String uniformName, int value1, int value2) {
+ setIntUniform(uniformName, value1, value2, 0, 0, 2);
+ }
+
+ /**
+ * Sets the uniform value corresponding to this color filter. If the effect does not have a
+ * uniform with that name or if the uniform is declared with a type other than an ivec3 or
+ * int[3] then an IllegalArgumentException is thrown.
+ *
+ * @param uniformName name matching the uniform declared in the AGSL program
+ */
+ public void setIntUniform(@NonNull String uniformName, int value1, int value2, int value3) {
+ setIntUniform(uniformName, value1, value2, value3, 0, 3);
+ }
+
+ /**
+ * Sets the uniform value corresponding to this color filter. If the effect does not have a
+ * uniform with that name or if the uniform is declared with a type other than an ivec4 or
+ * int[4] then an IllegalArgumentException is thrown.
+ *
+ * @param uniformName name matching the uniform declared in the AGSL program
+ */
+ public void setIntUniform(@NonNull String uniformName, int value1, int value2,
+ int value3, int value4) {
+ setIntUniform(uniformName, value1, value2, value3, value4, 4);
+ }
+
+ /**
+ * Sets the uniform value corresponding to this color filter. If the effect does not have a
+ * uniform with that name or if the uniform is declared with a type other than an int (for N=1),
+ * ivecN, or int[N] where N is the length of the values param then an IllegalArgumentException
+ * is thrown.
+ *
+ * @param uniformName name matching the uniform declared in the AGSL program
+ */
+ public void setIntUniform(@NonNull String uniformName, @NonNull int[] values) {
+ if (uniformName == null) {
+ throw new NullPointerException("The uniformName parameter must not be null");
+ }
+ if (values == null) {
+ throw new NullPointerException("The uniform values parameter must not be null");
+ }
+ nativeUpdateUniforms(mBuilderNativeInstance, uniformName, values);
+ }
+
+ private void setIntUniform(@NonNull String uniformName, int value1, int value2, int value3,
+ int value4, int count) {
+ if (uniformName == null) {
+ throw new NullPointerException("The uniformName parameter must not be null");
+ }
+ nativeUpdateUniforms(mBuilderNativeInstance, uniformName, value1, value2, value3, value4,
+ count);
+ }
+
+ /**
+ * Assigns the uniform shader to the provided shader parameter. If the shader program does not
+ * have a uniform shader with that name then an IllegalArgumentException is thrown.
+ *
+ * @param shaderName name matching the uniform declared in the AGSL program
+ * @param shader shader passed into the AGSL program for sampling
+ */
+ public void setInputShader(@NonNull String shaderName, @NonNull Shader shader) {
+ if (shaderName == null) {
+ throw new NullPointerException("The shaderName parameter must not be null");
+ }
+ if (shader == null) {
+ throw new NullPointerException("The shader parameter must not be null");
+ }
+ nativeUpdateChild(mBuilderNativeInstance, shaderName, shader.getNativeInstance());
+ }
+
+ /**
+ * Assigns the uniform color filter to the provided color filter parameter. If the shader
+ * program does not have a uniform color filter with that name then an IllegalArgumentException
+ * is thrown.
+ *
+ * @param filterName name matching the uniform declared in the AGSL program
+ * @param colorFilter filter passed into the AGSL program for sampling
+ */
+ public void setInputColorFilter(@NonNull String filterName, @NonNull ColorFilter colorFilter) {
+ if (filterName == null) {
+ throw new NullPointerException("The filterName parameter must not be null");
+ }
+ if (colorFilter == null) {
+ throw new NullPointerException("The colorFilter parameter must not be null");
+ }
+ nativeUpdateChild(mBuilderNativeInstance, filterName, colorFilter.getNativeInstance());
+ }
+
+ /**
+ * Assigns the uniform xfermode to the provided xfermode parameter. If the shader program does
+ * not have a uniform xfermode with that name then an IllegalArgumentException is thrown.
+ *
+ * @param xfermodeName name matching the uniform declared in the AGSL program
+ * @param xfermode xfermode function passed into the AGSL program for sampling
+ */
+ public void setInputXfermode(@NonNull String xfermodeName, @NonNull RuntimeXfermode xfermode) {
+ if (xfermodeName == null) {
+ throw new NullPointerException("The xfermodeName parameter must not be null");
+ }
+ if (xfermode == null) {
+ throw new NullPointerException("The xfermode parameter must not be null");
+ }
+ nativeUpdateChild(mBuilderNativeInstance, xfermodeName, xfermode.createNativeInstance());
+ }
+
+ /** @hide */
+ public long createNativeInstance() {
+ return nativeCreateNativeInstance(mBuilderNativeInstance);
+ }
+
+ /** @hide */
+ private static native long nativeGetFinalizer();
+ private static native long nativeCreateBlenderBuilder(String agsl);
+ private static native long nativeCreateNativeInstance(long builder);
+ private static native void nativeUpdateUniforms(
+ long builder, String uniformName, float[] uniforms, boolean isColor);
+ private static native void nativeUpdateUniforms(
+ long builder, String uniformName, float value1, float value2, float value3,
+ float value4, int count);
+ private static native void nativeUpdateUniforms(
+ long builder, String uniformName, int[] uniforms);
+ private static native void nativeUpdateUniforms(
+ long builder, String uniformName, int value1, int value2, int value3,
+ int value4, int count);
+ private static native void nativeUpdateChild(long builder, String childName, long child);
+
+}
diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java
index 6bb22a12280e..fb689e4cb9c2 100644
--- a/graphics/java/android/graphics/Xfermode.java
+++ b/graphics/java/android/graphics/Xfermode.java
@@ -21,9 +21,6 @@
package android.graphics;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.os.Build;
-
/**
* Xfermode is the base class for objects that are called to implement custom
* "transfer-modes" in the drawing pipeline. The static function Create(Modes)
@@ -31,8 +28,4 @@ import android.os.Build;
* specified in the Modes enum. When an Xfermode is assigned to a Paint, then
* objects drawn with that paint have the xfermode applied.
*/
-public class Xfermode {
- static final int DEFAULT = PorterDuff.Mode.SRC_OVER.nativeInt;
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- int porterDuffMode = DEFAULT;
-}
+public class Xfermode {}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
index 220fc6f82a71..819cf3492d24 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
@@ -94,7 +94,6 @@ class BackupHelper {
*/
void scheduleBackup() {
if (!mSaveEmbeddingState) {
- // TODO(b/289875940): enabled internally for broader testing.
return;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java
index cb280c530c1b..0f1246cd17e3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java
@@ -44,7 +44,6 @@ class ParcelableSplitContainerData implements Parcelable {
@NonNull
private final IBinder mSecondaryContainerToken;
- // TODO(b/289875940): making this as non-null once the tag can be auto-generated from the rule.
@Nullable
final String mSplitRuleTag;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java
index a79a89a210ac..bf342d76f4c2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java
@@ -25,6 +25,9 @@ import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* This class holds the Parcelable data of a {@link TaskFragmentContainer}.
*/
@@ -61,6 +64,12 @@ class ParcelableTaskFragmentContainerData implements Parcelable {
@NonNull
final Rect mLastRequestedBounds;
+ /**
+ * Individual associated activity tokens in different containers that should be finished on
+ * exit.
+ */
+ final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
+
ParcelableTaskFragmentContainerData(@NonNull IBinder token, @Nullable String overlayTag,
@Nullable IBinder associatedActivityToken) {
mToken = token;
@@ -74,6 +83,7 @@ class ParcelableTaskFragmentContainerData implements Parcelable {
mOverlayTag = in.readString();
mAssociatedActivityToken = in.readStrongBinder();
mLastRequestedBounds = in.readTypedObject(Rect.CREATOR);
+ in.readBinderList(mActivitiesToFinishOnExit);
}
public static final Creator<ParcelableTaskFragmentContainerData> CREATOR = new Creator<>() {
@@ -99,7 +109,7 @@ class ParcelableTaskFragmentContainerData implements Parcelable {
dest.writeString(mOverlayTag);
dest.writeStrongBinder(mAssociatedActivityToken);
dest.writeTypedObject(mLastRequestedBounds, flags);
+ dest.writeBinderList(mActivitiesToFinishOnExit);
}
-
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index faf73c24073f..5ba30ddd2f18 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -98,10 +98,20 @@ class SplitContainer {
mCurrentSplitAttributes = mDefaultSplitAttributes;
if (shouldFinishPrimaryWithSecondary(splitRule)) {
- mSecondaryContainer.addContainerToFinishOnExit(mPrimaryContainer);
+ addContainerToFinishOnExitWhenRestore(mSecondaryContainer, mPrimaryContainer);
}
if (shouldFinishSecondaryWithPrimary(splitRule)) {
- mPrimaryContainer.addContainerToFinishOnExit(mSecondaryContainer);
+ addContainerToFinishOnExitWhenRestore(mPrimaryContainer, mSecondaryContainer);
+ }
+ }
+
+ private void addContainerToFinishOnExitWhenRestore(
+ @NonNull TaskFragmentContainer containerToAdd,
+ @NonNull TaskFragmentContainer containerToFinish) {
+ // If an activity was already added to be finished after the restoration, then that's it.
+ // Otherwise, add the container to finish on exit.
+ if (!containerToAdd.hasActivityToFinishOnExit(containerToFinish)) {
+ containerToAdd.addContainerToFinishOnExit(containerToFinish);
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index dc1d983997c6..b3e003e7ad95 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -96,12 +96,6 @@ class TaskFragmentContainer {
new ArrayList<>();
/**
- * Individual associated activity tokens in different containers that should be finished on
- * exit.
- */
- private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
-
- /**
* The launch options that was used to create this container. Must not {@link Bundle#isEmpty()}
* for {@link #isOverlay()} container.
*/
@@ -114,7 +108,6 @@ class TaskFragmentContainer {
/**
* Windowing mode that was requested last via {@link android.window.WindowContainerTransaction}.
*/
- // TODO(b/289875940): review this and other field that might need to be moved in the base class.
@WindowingMode
private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED;
@@ -443,7 +436,7 @@ class TaskFragmentContainer {
// Remove the activity now because there can be a delay before the server callback.
mInfo.getActivities().remove(activityToken);
}
- mActivitiesToFinishOnExit.remove(activityToken);
+ mParcelableData.mActivitiesToFinishOnExit.remove(activityToken);
finishSelfWithActivityIfNeeded(wct, activityToken);
}
@@ -624,7 +617,20 @@ class TaskFragmentContainer {
if (mIsFinished) {
return;
}
- mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken());
+ mParcelableData.mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken());
+ }
+
+ /**
+ * Returns {@code true} if an Activity from the given {@code container} was added to be
+ * finished on exit. Otherwise, return {@code false}.
+ */
+ boolean hasActivityToFinishOnExit(@NonNull TaskFragmentContainer container) {
+ for (IBinder activity : mParcelableData.mActivitiesToFinishOnExit) {
+ if (container.hasActivity(activity)) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -634,7 +640,7 @@ class TaskFragmentContainer {
if (mIsFinished) {
return;
}
- mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken());
+ mParcelableData.mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken());
}
/** Removes all dependencies that should be finished when this container is finished. */
@@ -643,7 +649,7 @@ class TaskFragmentContainer {
return;
}
mContainersToFinishOnExit.clear();
- mActivitiesToFinishOnExit.clear();
+ mParcelableData.mActivitiesToFinishOnExit.clear();
}
/**
@@ -721,7 +727,7 @@ class TaskFragmentContainer {
mContainersToFinishOnExit.clear();
// Finish associated activities
- for (IBinder activityToken : mActivitiesToFinishOnExit) {
+ for (IBinder activityToken : mParcelableData.mActivitiesToFinishOnExit) {
final Activity activity = mController.getActivity(activityToken);
if (activity == null || activity.isFinishing()
|| controller.shouldRetainAssociatedActivity(this, activity)) {
@@ -729,7 +735,7 @@ class TaskFragmentContainer {
}
wct.finishActivity(activity.getActivityToken());
}
- mActivitiesToFinishOnExit.clear();
+ mParcelableData.mActivitiesToFinishOnExit.clear();
}
@GuardedBy("mController.mLock")
@@ -1082,7 +1088,7 @@ class TaskFragmentContainer {
+ " pendingAppearedActivities=" + mPendingAppearedActivities
+ (includeContainersToFinishOnExit ? " containersToFinishOnExit="
+ containersToFinishOnExitToString() : "")
- + " activitiesToFinishOnExit=" + mActivitiesToFinishOnExit
+ + " activitiesToFinishOnExit=" + mParcelableData.mActivitiesToFinishOnExit
+ " info=" + mInfo
+ "}";
}
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 249e9a26e6a6..7078d66d265c 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -661,4 +661,7 @@
<dimen name="desktop_windowing_education_promo_height">352dp</dimen>
<!-- The corner radius of the desktop windowing education promo. -->
<dimen name="desktop_windowing_education_promo_corner_radius">28dp</dimen>
+
+ <!-- The corner radius of freeform tasks in desktop windowing. -->
+ <dimen name="desktop_windowing_freeform_rounded_corner_radius">16dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
index 03e0ab0591a1..4300e84e8044 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
@@ -168,6 +168,16 @@ public class GroupedTaskInfo implements Parcelable {
}
/**
+ * @return The task info for the task in this group with the given {@code taskId}.
+ */
+ @Nullable
+ public TaskInfo getTaskById(int taskId) {
+ return mTasks.stream()
+ .filter(task -> task.taskId == taskId)
+ .findFirst().orElse(null);
+ }
+
+ /**
* Get all {@link RecentTaskInfo}s grouped together.
*/
@NonNull
@@ -176,6 +186,14 @@ public class GroupedTaskInfo implements Parcelable {
}
/**
+ * @return Whether this grouped task contains a task with the given {@code taskId}.
+ */
+ public boolean containsTask(int taskId) {
+ return mTasks.stream()
+ .anyMatch((task -> task.taskId == taskId));
+ }
+
+ /**
* Return {@link SplitBounds} if this is a split screen entry or {@code null}
*/
@Nullable
@@ -249,9 +267,10 @@ public class GroupedTaskInfo implements Parcelable {
return null;
}
return "id=" + taskInfo.taskId
- + " baseIntent=" + (taskInfo.baseIntent != null
- ? taskInfo.baseIntent.getComponent()
- : "null")
+ + " baseIntent=" +
+ (taskInfo.baseIntent != null && taskInfo.baseIntent.getComponent() != null
+ ? taskInfo.baseIntent.getComponent().flattenToString()
+ : "null")
+ " winMode=" + WindowConfiguration.windowingModeToString(
taskInfo.getWindowingMode());
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
index 23e7441ff86b..f14dfdbc7d30 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
@@ -50,6 +50,22 @@ abstract class ManageWindowsViewContainer(
fun createMenu(snapshotList: List<Pair<Int, TaskSnapshot>>,
onIconClickListener: ((Int) -> Unit),
onOutsideClickListener: (() -> Unit)): ManageWindowsView {
+ val bitmapList = snapshotList.map { (index, snapshot) ->
+ index to Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace)
+ }
+ return createAndShowMenuView(
+ bitmapList,
+ onIconClickListener,
+ onOutsideClickListener
+ )
+ }
+
+ /** Creates the menu view with the given bitmaps, and displays it. */
+ fun createAndShowMenuView(
+ snapshotList: List<Pair<Int, Bitmap?>>,
+ onIconClickListener: ((Int) -> Unit),
+ onOutsideClickListener: (() -> Unit)
+ ): ManageWindowsView {
menuView = ManageWindowsView(context, menuBackgroundColor).apply {
this.onOutsideClickListener = onOutsideClickListener
this.onIconClickListener = onIconClickListener
@@ -120,7 +136,7 @@ abstract class ManageWindowsViewContainer(
}
fun generateIconViews(
- snapshotList: List<Pair<Int, TaskSnapshot>>
+ snapshotList: List<Pair<Int, Bitmap?>>
) {
menuWidth = 0
menuHeight = 0
@@ -133,7 +149,7 @@ abstract class ManageWindowsViewContainer(
// Add each icon to the menu, adding a new row when needed.
for ((iconCount, taskInfoSnapshotPair) in snapshotList.withIndex()) {
val taskId = taskInfoSnapshotPair.first
- val snapshot = taskInfoSnapshotPair.second
+ val snapshotBitmap = taskInfoSnapshotPair.second
// Once a row is filled, make a new row and increase the menu height.
if (iconCount % MENU_MAX_ICONS_PER_ROW == 0) {
rowLayout = LinearLayout(context)
@@ -141,10 +157,7 @@ abstract class ManageWindowsViewContainer(
rootView.addView(rowLayout)
menuHeight += (instanceIconHeight + iconMargin).toInt()
}
- val snapshotBitmap = Bitmap.wrapHardwareBuffer(
- snapshot.hardwareBuffer,
- snapshot.colorSpace
- )
+
val croppedBitmap = snapshotBitmap?.let { cropBitmap(it) }
val scaledSnapshotBitmap = croppedBitmap?.let {
Bitmap.createScaledBitmap(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index f532be6b8277..12d20bf0e517 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -96,6 +96,14 @@ public class DisplayController {
}
/**
+ * Get all the displays from DisplayManager.
+ */
+ public Display[] getDisplays() {
+ final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ return displayManager.getDisplays();
+ }
+
+ /**
* Gets the DisplayLayout associated with a display.
*/
public @Nullable DisplayLayout getDisplayLayout(int displayId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 77e041ee7cdb..02df38e03d7c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -795,13 +795,14 @@ public abstract class WMShellBaseModule {
static KeyguardTransitionHandler provideKeyguardTransitionHandler(
ShellInit shellInit,
ShellController shellController,
+ DisplayController displayController,
Transitions transitions,
TaskStackListenerImpl taskStackListener,
@ShellMainThread Handler mainHandler,
@ShellMainThread ShellExecutor mainExecutor) {
return new KeyguardTransitionHandler(
- shellInit, shellController, transitions, taskStackListener, mainHandler,
- mainExecutor);
+ shellInit, shellController, displayController, transitions, taskStackListener,
+ mainHandler, mainExecutor);
}
@WMSingleton
@@ -1024,10 +1025,13 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static TaskStackTransitionObserver provideTaskStackTransitionObserver(
- Lazy<Transitions> transitions,
- ShellInit shellInit
+ ShellInit shellInit,
+ Lazy<ShellTaskOrganizer> shellTaskOrganizer,
+ ShellCommandHandler shellCommandHandler,
+ Lazy<Transitions> transitions
) {
- return new TaskStackTransitionObserver(transitions, shellInit);
+ return new TaskStackTransitionObserver(shellInit, shellTaskOrganizer, shellCommandHandler,
+ transitions);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index 2001f9743094..82c2ebc7ec77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -23,6 +23,7 @@ import android.os.Handler
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_OPEN
import android.window.DesktopModeFlags
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
@@ -95,7 +96,7 @@ class DesktopMixedTransitionHandler(
fun startLaunchTransition(
@WindowManager.TransitionType transitionType: Int,
wct: WindowContainerTransaction,
- taskId: Int,
+ taskId: Int?,
minimizingTaskId: Int? = null,
exitingImmersiveTask: Int? = null,
): IBinder {
@@ -216,12 +217,12 @@ class DesktopMixedTransitionHandler(
): Boolean {
// Check if there's also an immersive change during this launch.
val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask ->
- findDesktopTaskChange(info, exitingTask)
+ findTaskChange(info, exitingTask)
}
val minimizeChange = pending.minimizingTask?.let { minimizingTask ->
- findDesktopTaskChange(info, minimizingTask)
+ findTaskChange(info, minimizingTask)
}
- val launchChange = findDesktopTaskChange(info, pending.launchingTask)
+ val launchChange = findDesktopTaskLaunchChange(info, pending.launchingTask)
if (launchChange == null) {
check(minimizeChange == null)
check(immersiveExitChange == null)
@@ -291,7 +292,7 @@ class DesktopMixedTransitionHandler(
): Boolean {
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue) return false
- val minimizeChange = findDesktopTaskChange(info, pending.minimizingTask)
+ val minimizeChange = findTaskChange(info, pending.minimizingTask)
if (minimizeChange == null) {
logW("Should have minimizing desktop task")
return false
@@ -417,8 +418,24 @@ class DesktopMixedTransitionHandler(
}
}
- private fun findDesktopTaskChange(info: TransitionInfo, taskId: Int): TransitionInfo.Change? {
- return info.changes.firstOrNull { change -> change.taskInfo?.taskId == taskId }
+ private fun findTaskChange(info: TransitionInfo, taskId: Int): TransitionInfo.Change? =
+ info.changes.firstOrNull { change -> change.taskInfo?.taskId == taskId }
+
+ private fun findDesktopTaskLaunchChange(
+ info: TransitionInfo,
+ launchTaskId: Int?
+ ): TransitionInfo.Change? {
+ return if (launchTaskId != null) {
+ // Launching a known task (probably from background or moving to front), so
+ // specifically look for it.
+ findTaskChange(info, launchTaskId)
+ } else {
+ // Launching a new task, so the first opening freeform task.
+ info.changes.firstOrNull { change ->
+ change.mode == TRANSIT_OPEN
+ && change.taskInfo != null && change.taskInfo!!.isFreeform
+ }
+ }
}
private fun WindowContainerTransaction?.merge(
@@ -441,7 +458,7 @@ class DesktopMixedTransitionHandler(
/** A task is opening or moving to front. */
data class Launch(
override val transition: IBinder,
- val launchingTask: Int,
+ val launchingTask: Int?,
val minimizingTask: Int?,
val exitingImmersiveTask: Int?,
) : PendingMixedTransition()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 08ca55f93e3f..7fcb7678f6af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -411,6 +411,12 @@ class DesktopRepository (
desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString())
// Remove task from unminimized task if it is minimized.
unminimizeTask(displayId, taskId)
+ // Mark task as not in immersive if it was immersive.
+ setTaskInFullImmersiveState(
+ displayId = displayId,
+ taskId = taskId,
+ immersive = false
+ )
removeActiveTask(taskId)
removeVisibleTask(taskId)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
@@ -487,6 +493,9 @@ class DesktopRepository (
mainCoroutineScope.launch {
try {
persistentRepository.addOrUpdateDesktop(
+ // Use display id as desktop id for now since only once desktop per display
+ // is supported.
+ desktopId = displayId,
visibleTasks = desktopTaskDataByDisplayIdCopy.visibleTasks,
minimizedTasks = desktopTaskDataByDisplayIdCopy.minimizedTasks,
freeformTasksInZOrder = desktopTaskDataByDisplayIdCopy.freeformTasksInZOrder
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 6928c255edde..4db0be5c9025 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
@@ -89,7 +89,6 @@ import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
-import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
@@ -618,25 +617,18 @@ class DesktopTasksController(
private fun moveBackgroundTaskToFront(taskId: Int, remoteTransition: RemoteTransition?) {
logV("moveBackgroundTaskToFront taskId=%s", taskId)
val wct = WindowContainerTransaction()
- // TODO: b/342378842 - Instead of using default display, support multiple displays
- val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
- wct = wct,
- displayId = DEFAULT_DISPLAY,
- excludeTaskId = taskId,
- )
wct.startTask(
taskId,
ActivityOptions.makeBasic().apply {
launchWindowingMode = WINDOWING_MODE_FREEFORM
}.toBundle(),
)
- val transition = startLaunchTransition(
+ startLaunchTransition(
TRANSIT_OPEN,
wct,
taskId,
remoteTransition = remoteTransition
)
- exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
}
/**
@@ -655,47 +647,53 @@ class DesktopTasksController(
}
val wct = WindowContainerTransaction()
wct.reorder(taskInfo.token, true /* onTop */, true /* includingParents */)
- val result = desktopImmersiveController.exitImmersiveIfApplicable(
- wct = wct,
- displayId = taskInfo.displayId,
- excludeTaskId = taskInfo.taskId,
- )
- val exitResult = if (result is ExitResult.Exit) { result } else { null }
- val transition = startLaunchTransition(
+ startLaunchTransition(
transitionType = TRANSIT_TO_FRONT,
wct = wct,
- taskId = taskInfo.taskId,
- exitingImmersiveTask = exitResult?.exitingTask,
+ launchingTaskId = taskInfo.taskId,
remoteTransition = remoteTransition,
displayId = taskInfo.displayId,
)
- exitResult?.runOnTransitionStart?.invoke(transition)
}
private fun startLaunchTransition(
transitionType: Int,
wct: WindowContainerTransaction,
- taskId: Int,
- exitingImmersiveTask: Int? = null,
+ launchingTaskId: Int?,
remoteTransition: RemoteTransition? = null,
displayId: Int = DEFAULT_DISPLAY,
): IBinder {
- val taskIdToMinimize = addAndGetMinimizeChanges(displayId, wct, taskId)
+ val taskIdToMinimize = if (launchingTaskId != null) {
+ addAndGetMinimizeChanges(displayId, wct, newTaskId = launchingTaskId)
+ } else {
+ logW("Starting desktop task launch without checking the task-limit")
+ // TODO(b/378920066): This currently does not respect the desktop window limit.
+ // It's possible that |launchingTaskId| is null when launching using an intent, and
+ // the task-limit should be respected then too.
+ null
+ }
+ val exitImmersiveResult = desktopImmersiveController.exitImmersiveIfApplicable(
+ wct = wct,
+ displayId = displayId,
+ excludeTaskId = launchingTaskId,
+ )
if (remoteTransition == null) {
val t = desktopMixedTransitionHandler.startLaunchTransition(
transitionType = transitionType,
wct = wct,
- taskId = taskId,
+ taskId = launchingTaskId,
minimizingTaskId = taskIdToMinimize,
- exitingImmersiveTask = exitingImmersiveTask,
+ exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask,
)
taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
+ exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
return t
}
if (taskIdToMinimize == null) {
val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
remoteTransitionHandler.setTransition(t)
+ exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
return t
}
val remoteTransitionHandler =
@@ -704,6 +702,7 @@ class DesktopTasksController(
val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
remoteTransitionHandler.setTransition(t)
taskIdToMinimize.let { addPendingMinimizeTransition(t, it) }
+ exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
return t
}
@@ -1472,10 +1471,14 @@ class DesktopTasksController(
)
}
WINDOWING_MODE_FREEFORM -> {
- // TODO(b/336289597): This currently does not respect the desktop window limit.
val wct = WindowContainerTransaction()
wct.sendPendingIntent(launchIntent, fillIn, options.toBundle())
- transitions.startTransition(TRANSIT_OPEN, wct, null)
+ startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ launchingTaskId = null,
+ displayId = callingTaskInfo.displayId
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index e848b889b314..2ae9828ca0db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -254,8 +254,13 @@ public class FreeformTaskTransitionHandler
finishT.hide(sc);
final Rect startBounds = new Rect(change.getStartAbsBounds());
animator.addUpdateListener(animation -> {
- t.setPosition(sc, startBounds.left,
- startBounds.top + (animation.getAnimatedFraction() * screenHeight));
+ final float newTop = startBounds.top + (animation.getAnimatedFraction() * screenHeight);
+ t.setPosition(sc, startBounds.left, newTop);
+ if (newTop > screenHeight) {
+ // At this point the task surface is off-screen, so hide it to prevent flicker
+ // failures. See b/377651666.
+ t.hide(sc);
+ }
t.apply();
});
animator.addListener(
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 b618bf1215ac..319bfac734ba 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
@@ -42,6 +42,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
+import android.view.Display;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.IRemoteTransition;
@@ -54,6 +55,7 @@ import android.window.WindowContainerTransaction;
import com.android.internal.protolog.ProtoLog;
import com.android.window.flags.Flags;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
@@ -80,6 +82,8 @@ public class KeyguardTransitionHandler
private final Transitions mTransitions;
private final ShellController mShellController;
+
+ private final DisplayController mDisplayController;
private final Handler mMainHandler;
private final ShellExecutor mMainExecutor;
@@ -121,12 +125,14 @@ public class KeyguardTransitionHandler
public KeyguardTransitionHandler(
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
+ @NonNull DisplayController displayController,
@NonNull Transitions transitions,
@NonNull TaskStackListenerImpl taskStackListener,
@NonNull Handler mainHandler,
@NonNull ShellExecutor mainExecutor) {
mTransitions = transitions;
mShellController = shellController;
+ mDisplayController = displayController;
mMainHandler = mainHandler;
mMainExecutor = mainExecutor;
mTaskStackListener = taskStackListener;
@@ -429,10 +435,10 @@ public class KeyguardTransitionHandler
@Override
public void startKeyguardTransition(boolean keyguardShowing, boolean aodShowing) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final KeyguardState keyguardState =
- new KeyguardState.Builder(android.view.Display.DEFAULT_DISPLAY)
- .setKeyguardShowing(keyguardShowing).setAodShowing(aodShowing).build();
- wct.addKeyguardState(keyguardState);
+ for (Display display : mDisplayController.getDisplays()) {
+ wct.addKeyguardState(new KeyguardState.Builder(display.getDisplayId())
+ .setKeyguardShowing(keyguardShowing).setAodShowing(aodShowing).build());
+ }
mMainExecutor.execute(() -> {
mTransitions.startTransition(keyguardShowing ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
wct, KeyguardTransitionHandler.this);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
index eb33ff4c1c8e..35c90acd306f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
@@ -52,6 +52,9 @@ public class PipEnterAnimator extends ValueAnimator {
private final SurfaceControl.Transaction mStartTransaction;
private final SurfaceControl.Transaction mFinishTransaction;
+ private final int mCornerRadius;
+ private final int mShadowRadius;
+
// Bounds updated by the evaluator as animator is running.
private final Rect mAnimatedRect = new Rect();
@@ -128,6 +131,8 @@ public class PipEnterAnimator extends ValueAnimator {
final int enterAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipEnterAnimationDuration);
+ mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
+ mShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius);
setDuration(enterAnimationDuration);
setFloatValues(0f, 1f);
setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@@ -177,6 +182,8 @@ public class PipEnterAnimator extends ValueAnimator {
mTransformTensor.postRotate(degrees);
tx.setMatrix(mLeash, mTransformTensor, mMatrixTmp);
+ tx.setCornerRadius(mLeash, mCornerRadius).setShadowRadius(mLeash, mShadowRadius);
+
if (mContentOverlay != null) {
mContentOverlay.onAnimationUpdate(tx, 1f / scaleX, fraction, mEndBounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index 4558a9f141c8..06e8349259b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
@@ -29,6 +29,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.shared.animation.Interpolators;
@@ -50,6 +51,9 @@ public class PipResizeAnimator extends ValueAnimator {
private Runnable mAnimationEndCallback;
private RectEvaluator mRectEvaluator;
+ private final int mCornerRadius;
+ private final int mShadowRadius;
+
// Bounds relative to which scaling/cropping must be done.
private final Rect mBaseBounds = new Rect();
@@ -74,7 +78,8 @@ public class PipResizeAnimator extends ValueAnimator {
mAnimationStartCallback.run();
}
if (mStartTx != null) {
- setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta);
+ setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta,
+ mCornerRadius, mShadowRadius);
mStartTx.apply();
}
}
@@ -83,7 +88,8 @@ public class PipResizeAnimator extends ValueAnimator {
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (mFinishTx != null) {
- setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f);
+ setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f,
+ mCornerRadius, mShadowRadius);
}
if (mAnimationEndCallback != null) {
mAnimationEndCallback.run();
@@ -99,7 +105,8 @@ public class PipResizeAnimator extends ValueAnimator {
mSurfaceControlTransactionFactory.getTransaction();
final float fraction = getAnimatedFraction();
final float degrees = (1.0f - fraction) * mDelta;
- setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees);
+ setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees,
+ mCornerRadius, mShadowRadius);
tx.apply();
}
};
@@ -128,6 +135,9 @@ public class PipResizeAnimator extends ValueAnimator {
mRectEvaluator = new RectEvaluator(mAnimatedRect);
+ mCornerRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
+ mShadowRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius);
+
setObjectValues(startBounds, endBounds);
setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
addListener(mAnimatorListener);
@@ -152,7 +162,7 @@ public class PipResizeAnimator extends ValueAnimator {
* @param degrees degrees of rotation - counter-clockwise is positive by convention.
*/
private static void setBoundsAndRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect baseBounds, Rect targetBounds, float degrees) {
+ Rect baseBounds, Rect targetBounds, float degrees, int cornerRadius, int shadowRadius) {
Matrix transformTensor = new Matrix();
final float[] mMatrixTmp = new float[9];
final float scaleX = (float) targetBounds.width() / baseBounds.width();
@@ -162,7 +172,9 @@ public class PipResizeAnimator extends ValueAnimator {
transformTensor.postTranslate(targetBounds.left, targetBounds.top);
transformTensor.postRotate(degrees, targetBounds.centerX(), targetBounds.centerY());
- tx.setMatrix(leash, transformTensor, mMatrixTmp);
+ tx.setMatrix(leash, transformTensor, mMatrixTmp)
+ .setCornerRadius(leash, cornerRadius)
+ .setShadowRadius(leash, shadowRadius);
}
@VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index e901c39b8792..6d2df952ee58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip2.phone;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
@@ -293,6 +294,11 @@ public class PipController implements ConfigurationChangeListener,
// Update the display layout caches even if we are not in PiP.
setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
+ if (toRotation != ROTATION_UNDEFINED) {
+ // Make sure we rotate to final rotation ourselves in case display change is coming
+ // from the remote rotation as a part of an already collecting transition.
+ mPipDisplayLayoutState.rotateTo(toRotation);
+ }
if (!mPipTransitionState.isInPip()) {
// Skip the PiP-relevant updates if we aren't in a valid PiP state.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 3738353dd0a3..fd387d1811fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -785,8 +785,16 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private void handleFlingTransition(SurfaceControl.Transaction startTx,
SurfaceControl.Transaction finishTx, Rect destinationBounds) {
- startTx.setPosition(mPipTransitionState.getPinnedTaskLeash(),
- destinationBounds.left, destinationBounds.top);
+ SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
+ int cornerRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
+ int shadowRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius);
+
+ // merge transactions so everything is done on startTx
+ startTx.merge(finishTx);
+
+ startTx.setPosition(pipLeash, destinationBounds.left, destinationBounds.top)
+ .setCornerRadius(pipLeash, cornerRadius)
+ .setShadowRadius(pipLeash, shadowRadius);
startTx.apply();
// All motion operations have actually finished, so make bounds cache updates.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index d415c10b0cf8..08e672790da6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -241,7 +241,7 @@ public class PipTransition extends PipTransitionController implements
extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
mPipTransitionState.setState(PipTransitionState.ENTERING_PIP, extra);
- if (mPipTransitionState.isInSwipePipToHomeTransition()) {
+ if (isInSwipePipToHomeTransition()) {
// If this is the second transition as a part of swipe PiP to home cuj,
// handle this transition as a special case with no-op animation.
return handleSwipePipToHomeTransition(info, startTransaction, finishTransaction,
@@ -702,6 +702,13 @@ public class PipTransition extends PipTransitionController implements
@NonNull TransitionInfo.Change pipChange) {
TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
int startRotation = pipChange.getStartRotation();
+ if (pipChange.getEndRotation() != ROTATION_UNDEFINED
+ && startRotation != pipChange.getEndRotation()) {
+ // If PiP change was collected along with the display change and the orientation change
+ // happened in sync with the PiP change, then do not treat this as fixed-rotation case.
+ return ROTATION_0;
+ }
+
int endRotation = fixedRotationChange != null
? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation();
int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 49cf8ae81aa8..35e6c8dd6580 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -46,6 +46,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
"ShellBackPreview"),
WM_SHELL_RECENT_TASKS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_TASK_OBSERVER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM_SHELL),
// TODO(b/282232877): turn logToLogcat to false.
WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM_SHELL),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index b58f0681c571..68dc0f27bca1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -45,9 +45,15 @@ oneway interface IRecentTasksListener {
*/
void onRunningTaskChanged(in RunningTaskInfo taskInfo);
- /** A task has moved to front. */
- void onTaskMovedToFront(in GroupedTaskInfo[] visibleTasks);
+ /** A task has moved to front. Only used if enableShellTopTaskTracking() is disabled. */
+ void onTaskMovedToFront(in GroupedTaskInfo taskToFront);
- /** A task info has changed. */
+ /** A task info has changed. Only used if enableShellTopTaskTracking() is disabled. */
void onTaskInfoChanged(in RunningTaskInfo taskInfo);
+
+ /**
+ * If enableShellTopTaskTracking() is enabled, this reports the set of all visible tasks.
+ * Otherwise, this reports only the new top most visible task.
+ */
+ void onVisibleTasksChanged(in GroupedTaskInfo[] visibleTasks);
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 9911669d2cb8..6da4f510ab77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -17,14 +17,18 @@
package com.android.wm.shell.recents;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.PackageManager.FEATURE_PC;
+import static com.android.wm.shell.Flags.enableShellTopTaskTracking;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_OBSERVER;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
import android.Manifest;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.app.IApplicationThread;
import android.app.KeyguardManager;
@@ -65,7 +69,6 @@ import com.android.wm.shell.shared.split.SplitBounds;
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.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -111,6 +114,11 @@ public class RecentTasksController implements TaskStackListenerCallback,
private final Map<Integer, SplitBounds> mTaskSplitBoundsMap = new HashMap<>();
/**
+ * Cached list of the visible tasks, sorted from top most to bottom most.
+ */
+ private final List<RunningTaskInfo> mVisibleTasks = new ArrayList<>();
+
+ /**
* Creates {@link RecentTasksController}, returns {@code null} if the feature is not
* supported.
*/
@@ -170,10 +178,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
mShellCommandHandler.addDumpCallback(this::dump, this);
mTaskStackListener.addListener(this);
mDesktopRepository.ifPresent(it -> it.addActiveTaskListener(this));
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTaskStackTransitionObserver.addTaskStackTransitionObserverListener(this,
- mMainExecutor);
- }
+ mTaskStackTransitionObserver.addTaskStackTransitionObserverListener(this,
+ mMainExecutor);
mContext.getSystemService(KeyguardManager.class).addKeyguardLockedStateListener(
mMainExecutor, isKeyguardLocked -> notifyRecentTasksChanged());
}
@@ -205,7 +211,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
mTaskSplitBoundsMap.put(taskId1, splitBounds);
mTaskSplitBoundsMap.put(taskId2, splitBounds);
notifyRecentTasksChanged();
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Add split pair: %d, %d, %s",
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Add split pair: %d, %d, %s",
taskId1, taskId2, splitBounds);
return true;
}
@@ -221,7 +227,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
mTaskSplitBoundsMap.remove(taskId);
mTaskSplitBoundsMap.remove(pairedTaskId);
notifyRecentTasksChanged();
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Remove split pair: %d, %d",
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Remove split pair: %d, %d",
taskId, pairedTaskId);
}
}
@@ -234,7 +240,17 @@ public class RecentTasksController implements TaskStackListenerCallback,
// We could do extra verification of requiring both taskIds of a pair and verifying that
// the same split bounds object is returned... but meh. Seems unnecessary.
- return mTaskSplitBoundsMap.get(taskId);
+ SplitBounds splitBounds = mTaskSplitBoundsMap.get(taskId);
+ if (splitBounds != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "getSplitBoundsForTaskId: taskId=%d splitBoundsTasks=[%d, %d]", taskId,
+ splitBounds.leftTopTaskId, splitBounds.rightBottomTaskId);
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "getSplitBoundsForTaskId: expected split bounds for taskId=%d but not found",
+ taskId);
+ }
+ return splitBounds;
}
@Override
@@ -249,7 +265,10 @@ public class RecentTasksController implements TaskStackListenerCallback,
@Override
public void onTaskStackChanged() {
- notifyRecentTasksChanged();
+ if (!enableShellTopTaskTracking()) {
+ // Skip notifying recent tasks changed whenever task stack changes
+ notifyRecentTasksChanged();
+ }
}
@Override
@@ -263,15 +282,18 @@ public class RecentTasksController implements TaskStackListenerCallback,
notifyRecentTasksChanged();
}
- public void onTaskAdded(ActivityManager.RunningTaskInfo taskInfo) {
+ public void onTaskAdded(RunningTaskInfo taskInfo) {
notifyRunningTaskAppeared(taskInfo);
}
- public void onTaskRemoved(ActivityManager.RunningTaskInfo taskInfo) {
+ public void onTaskRemoved(RunningTaskInfo taskInfo) {
// Remove any split pairs associated with this task
removeSplitPair(taskInfo.taskId);
- notifyRecentTasksChanged();
notifyRunningTaskVanished(taskInfo);
+ if (!enableShellTopTaskTracking()) {
+ // Only notify recent tasks changed if we aren't already notifying the visible tasks
+ notifyRecentTasksChanged();
+ }
}
/**
@@ -279,7 +301,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
*
* This currently includes windowing mode and visibility.
*/
- public void onTaskRunningInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ public void onTaskRunningInfoChanged(RunningTaskInfo taskInfo) {
notifyRecentTasksChanged();
notifyRunningTaskChanged(taskInfo);
}
@@ -290,14 +312,21 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
@Override
+ public void onTaskMovedToFrontThroughTransition(RunningTaskInfo runningTaskInfo) {
+ notifyTaskMovedToFront(runningTaskInfo);
+ }
+
+ @Override
public void onTaskChangedThroughTransition(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
notifyTaskInfoChanged(taskInfo);
}
@Override
- public void onTaskMovedToFrontThroughTransition(
- ActivityManager.RunningTaskInfo runningTaskInfo) {
- notifyTaskMovedToFront(runningTaskInfo);
+ public void onVisibleTasksChanged(@NonNull List<? extends RunningTaskInfo> visibleTasks) {
+ mVisibleTasks.clear();
+ mVisibleTasks.addAll(visibleTasks);
+ // Notify with all the info and not just the running task info
+ notifyVisibleTasksChanged(visibleTasks);
}
@VisibleForTesting
@@ -316,7 +345,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
/**
* Notify the running task listener that a task appeared on desktop environment.
*/
- private void notifyRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
+ private void notifyRunningTaskAppeared(RunningTaskInfo taskInfo) {
if (mListener == null
|| !shouldEnableRunningTasksForDesktopMode()
|| taskInfo.realActivity == null) {
@@ -330,9 +359,25 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
/**
+ * Notify the running task listener that a task was changed on desktop environment.
+ */
+ private void notifyRunningTaskChanged(RunningTaskInfo taskInfo) {
+ if (mListener == null
+ || !shouldEnableRunningTasksForDesktopMode()
+ || taskInfo.realActivity == null) {
+ return;
+ }
+ try {
+ mListener.onRunningTaskChanged(taskInfo);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed call onRunningTaskChanged", e);
+ }
+ }
+
+ /**
* Notify the running task listener that a task was removed on desktop environment.
*/
- private void notifyRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ private void notifyRunningTaskVanished(RunningTaskInfo taskInfo) {
if (mListener == null
|| !shouldEnableRunningTasksForDesktopMode()
|| taskInfo.realActivity == null) {
@@ -346,25 +391,30 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
/**
- * Notify the running task listener that a task was changed on desktop environment.
+ * Notify the recents task listener that a task moved to front via a transition.
*/
- private void notifyRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ private void notifyTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
if (mListener == null
- || !shouldEnableRunningTasksForDesktopMode()
- || taskInfo.realActivity == null) {
+ || !DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue()
+ || taskInfo.realActivity == null
+ || enableShellTopTaskTracking()) {
return;
}
try {
- mListener.onRunningTaskChanged(taskInfo);
+ mListener.onTaskMovedToFront(GroupedTaskInfo.forFullscreenTasks(taskInfo));
} catch (RemoteException e) {
- Slog.w(TAG, "Failed call onRunningTaskChanged", e);
+ Slog.w(TAG, "Failed call onTaskMovedToFront", e);
}
}
+ /**
+ * Notify the recents task listener that a task changed via a transition.
+ */
private void notifyTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
if (mListener == null
|| !DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue()
- || taskInfo.realActivity == null) {
+ || taskInfo.realActivity == null
+ || enableShellTopTaskTracking()) {
return;
}
try {
@@ -374,17 +424,21 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
}
- private void notifyTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
+ /**
+ * Notifies that the test of visible tasks have changed.
+ */
+ private void notifyVisibleTasksChanged(@NonNull List<? extends RunningTaskInfo> visibleTasks) {
if (mListener == null
|| !DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue()
- || taskInfo.realActivity == null) {
+ || !enableShellTopTaskTracking()) {
return;
}
try {
- GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo);
- mListener.onTaskMovedToFront(new GroupedTaskInfo[]{ runningTask });
+ // Compute the visible recent tasks in order, and move the task to the top
+ mListener.onVisibleTasksChanged(generateList(visibleTasks)
+ .toArray(new GroupedTaskInfo[0]));
} catch (RemoteException e) {
- Slog.w(TAG, "Failed call onTaskMovedToFront", e);
+ Slog.w(TAG, "Failed call onVisibleTasksChanged", e);
}
}
@@ -397,6 +451,11 @@ public class RecentTasksController implements TaskStackListenerCallback,
@VisibleForTesting
void registerRecentTasksListener(IRecentTasksListener listener) {
mListener = listener;
+ if (enableShellTopTaskTracking()) {
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, "registerRecentTasksListener");
+ // Post a notification for the current set of visible tasks
+ mMainExecutor.executeDelayed(() -> notifyVisibleTasksChanged(mVisibleTasks), 0);
+ }
}
@VisibleForTesting
@@ -411,14 +470,18 @@ public class RecentTasksController implements TaskStackListenerCallback,
@VisibleForTesting
ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
- // Note: the returned task list is from the most-recent to least-recent order
- final List<RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
- maxNum, flags, userId);
+ // Note: the returned task list is ordered from the most-recent to least-recent order
+ return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId));
+ }
+ /**
+ * Generates a list of GroupedTaskInfos for the given list of tasks.
+ */
+ private <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks) {
// Make a mapping of task id -> task info
final SparseArray<TaskInfo> rawMapping = new SparseArray<>();
- for (int i = 0; i < rawList.size(); i++) {
- final TaskInfo taskInfo = rawList.get(i);
+ for (int i = 0; i < tasks.size(); i++) {
+ final TaskInfo taskInfo = tasks.get(i);
rawMapping.put(taskInfo.taskId, taskInfo);
}
@@ -427,10 +490,10 @@ public class RecentTasksController implements TaskStackListenerCallback,
int mostRecentFreeformTaskIndex = Integer.MAX_VALUE;
+ ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>();
// Pull out the pairs as we iterate back in the list
- ArrayList<GroupedTaskInfo> recentTasks = new ArrayList<>();
- for (int i = 0; i < rawList.size(); i++) {
- final RecentTaskInfo taskInfo = rawList.get(i);
+ for (int i = 0; i < tasks.size(); i++) {
+ final TaskInfo taskInfo = tasks.get(i);
if (!rawMapping.contains(taskInfo.taskId)) {
// If it's not in the mapping, then it was already paired with another task
continue;
@@ -441,7 +504,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
&& mDesktopRepository.get().isActiveTask(taskInfo.taskId)) {
// Freeform tasks will be added as a separate entry
if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
- mostRecentFreeformTaskIndex = recentTasks.size();
+ mostRecentFreeformTaskIndex = groupedTasks.size();
}
// If task has their app bounds set to null which happens after reboot, set the
// app bounds to persisted lastFullscreenBounds. Also set the position in parent
@@ -461,36 +524,34 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
- if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(
- pairedTaskId)) {
+ if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(pairedTaskId)) {
final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
rawMapping.remove(pairedTaskId);
- recentTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
+ groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
mTaskSplitBoundsMap.get(pairedTaskId)));
} else {
- recentTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
+ // TODO(346588978): Consolidate multiple visible fullscreen tasks into the same
+ // grouped task
+ groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
}
}
// Add a special entry for freeform tasks
if (!freeformTasks.isEmpty()) {
- recentTasks.add(mostRecentFreeformTaskIndex,
+ groupedTasks.add(mostRecentFreeformTaskIndex,
GroupedTaskInfo.forFreeformTasks(
freeformTasks,
minimizedFreeformTasks));
}
- return recentTasks;
- }
+ if (enableShellTopTaskTracking()) {
+ // We don't current send pinned tasks as a part of recent or running tasks, so remove
+ // them from the list here
+ groupedTasks.removeIf(
+ gti -> gti.getTaskInfo1().getWindowingMode() == WINDOWING_MODE_PINNED);
+ }
- /**
- * Returns the top running leaf task.
- */
- @Nullable
- public ActivityManager.RunningTaskInfo getTopRunningTask() {
- List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(1,
- false /* filterOnlyVisibleRecents */);
- return tasks.isEmpty() ? null : tasks.get(0);
+ return groupedTasks;
}
/**
@@ -498,12 +559,13 @@ public class RecentTasksController implements TaskStackListenerCallback,
* NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task.
*/
@Nullable
- public ActivityManager.RunningTaskInfo getTopRunningTask(
+ public RunningTaskInfo getTopRunningTask(
@Nullable WindowContainerToken ignoreTaskToken) {
- List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(2,
- false /* filterOnlyVisibleRecents */);
+ final List<RunningTaskInfo> tasks = enableShellTopTaskTracking()
+ ? mVisibleTasks
+ : mActivityTaskManager.getTasks(2, false /* filterOnlyVisibleRecents */);
for (int i = 0; i < tasks.size(); i++) {
- final ActivityManager.RunningTaskInfo task = tasks.get(i);
+ final RunningTaskInfo task = tasks.get(i);
if (task.token.equals(ignoreTaskToken)) {
continue;
}
@@ -541,7 +603,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
/**
- * Find the background task that match the given taskId.
+ * Find the background task (in the recent tasks list) that matches the given taskId.
*/
@Nullable
public RecentTaskInfo findTaskInBackground(int taskId) {
@@ -638,29 +700,34 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
@Override
- public void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
+ public void onRunningTaskAppeared(RunningTaskInfo taskInfo) {
mListener.call(l -> l.onRunningTaskAppeared(taskInfo));
}
@Override
- public void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ public void onRunningTaskVanished(RunningTaskInfo taskInfo) {
mListener.call(l -> l.onRunningTaskVanished(taskInfo));
}
@Override
- public void onRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ public void onRunningTaskChanged(RunningTaskInfo taskInfo) {
mListener.call(l -> l.onRunningTaskChanged(taskInfo));
}
@Override
- public void onTaskMovedToFront(GroupedTaskInfo[] taskInfo) {
- mListener.call(l -> l.onTaskMovedToFront(taskInfo));
+ public void onTaskMovedToFront(GroupedTaskInfo taskToFront) {
+ mListener.call(l -> l.onTaskMovedToFront(taskToFront));
}
@Override
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
mListener.call(l -> l.onTaskInfoChanged(taskInfo));
}
+
+ @Override
+ public void onVisibleTasksChanged(GroupedTaskInfo[] visibleTasks) {
+ mListener.call(l -> l.onVisibleTasksChanged(visibleTasks));
+ }
};
public IRecentTasksImpl(RecentTasksController controller) {
@@ -714,12 +781,12 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
@Override
- public ActivityManager.RunningTaskInfo[] getRunningTasks(int maxNum) {
- final ActivityManager.RunningTaskInfo[][] tasks =
- new ActivityManager.RunningTaskInfo[][]{null};
+ public RunningTaskInfo[] getRunningTasks(int maxNum) {
+ final RunningTaskInfo[][] tasks =
+ new RunningTaskInfo[][]{null};
executeRemoteCallWithTaskPermission(mController, "getRunningTasks",
(controller) -> tasks[0] = ActivityTaskManager.getInstance().getTasks(maxNum)
- .toArray(new ActivityManager.RunningTaskInfo[0]),
+ .toArray(new RunningTaskInfo[0]),
true /* blocking */);
return tasks[0];
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
index d28a462546f9..93f2e4cf0e45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -17,37 +17,162 @@
package com.android.wm.shell.recents
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.os.IBinder
import android.util.ArrayMap
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.DesktopModeFlags
import android.window.TransitionInfo
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.Flags.enableShellTopTaskTracking
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_OBSERVER
import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import dagger.Lazy
+import java.io.PrintWriter
+import java.util.StringJoiner
import java.util.concurrent.Executor
/**
- * A [Transitions.TransitionObserver] that observes shell transitions and sends updates to listeners
- * about task stack changes.
+ * A [Transitions.TransitionObserver] that observes shell transitions, tracks the visible tasks
+ * and notifies listeners whenever the visible tasks change (at the start and end of a transition).
*
- * TODO(346588978) Move split/pip signals here as well so that launcher don't need to handle it
+ * This can be replaced once we have a generalized task repository tracking visible tasks.
*/
class TaskStackTransitionObserver(
+ shellInit: ShellInit,
+ private val shellTaskOrganizer: Lazy<ShellTaskOrganizer>,
+ private val shellCommandHandler: ShellCommandHandler,
private val transitions: Lazy<Transitions>,
- shellInit: ShellInit
-) : Transitions.TransitionObserver {
+) : Transitions.TransitionObserver, ShellTaskOrganizer.TaskVanishedListener {
+
+ // List of currently visible tasks sorted in z-order from top-most to bottom-most, only used
+ // when Flags.enableShellTopTaskTracking() is enabled.
+ private var visibleTasks: MutableList<RunningTaskInfo> = mutableListOf()
+ private val pendingCloseTasks: MutableList<RunningTaskInfo> = mutableListOf()
+ // Set of listeners to notify when the visible tasks change
private val taskStackTransitionObserverListeners =
ArrayMap<TaskStackTransitionObserverListener, Executor>()
+ // Used to filter out leaf-tasks
+ private val leafTaskFilter: TransitionUtil.LeafTaskFilter = TransitionUtil.LeafTaskFilter()
init {
shellInit.addInitCallback(::onInit, this)
}
fun onInit() {
+ shellTaskOrganizer.get().addTaskVanishedListener(this)
+ shellCommandHandler.addDumpCallback(::dump, this)
transitions.get().registerObserver(this)
+
+ // TODO(346588978): We need to update the running tasks once the ShellTaskOrganizer is
+ // registered since there is no existing transition (yet) corresponding for the already
+ // visible tasks
+ }
+
+ /**
+ * This method handles transition ready when only
+ * DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL is set.
+ */
+ private fun onDesktopOnlyFlagTransitionReady(info: TransitionInfo) {
+ for (change in info.changes) {
+ if (change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0) {
+ continue
+ }
+
+ val taskInfo = change.taskInfo
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ continue
+ }
+
+ // Find the first task that is opening, this should be the one at the front after
+ // the transition
+ if (TransitionUtil.isOpeningType(change.mode)) {
+ notifyOnTaskMovedToFront(taskInfo)
+ break
+ } else if (change.mode == TRANSIT_CHANGE) {
+ notifyOnTaskChanged(taskInfo)
+ }
+ }
+ }
+
+ /**
+ * This method handles transition ready when Flags.enableShellTopTaskTracking() is set.
+ */
+ private fun onShellTopTaskTrackerFlagTransitionReady(info: TransitionInfo) {
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, "Transition ready: %d", info.debugId)
+
+ // Filter out non-leaf tasks (we will likely need them later, but visible task tracking
+ // is currently used only for visible leaf tasks)
+ val changesReversed = mutableListOf<TransitionInfo.Change>()
+ for (change in info.changes) {
+ if (!leafTaskFilter.test(change)) {
+ // Not a leaf task
+ continue
+ }
+ changesReversed.add(0, change)
+ }
+
+ // We iterate the change list in reverse order because changes are sorted top to bottom and
+ // we want to update the lists such that the top most tasks are inserted at the front last
+ var notifyChanges = false
+ for (change in changesReversed) {
+ val taskInfo = change.taskInfo
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ // Not a valid task
+ continue
+ }
+
+ if (TransitionUtil.isClosingMode(change.mode)) {
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, "\tClosing task=%d", taskInfo.taskId)
+
+ // Closing task's visibilities are not committed until after the transition
+ // completes, so track such tasks so that we can notify on finish
+ if (!pendingCloseTasks.any { it.taskId == taskInfo.taskId }) {
+ pendingCloseTasks.add(taskInfo)
+ }
+ } else if (TransitionUtil.isOpeningMode(change.mode)
+ || TransitionUtil.isOrderOnly(change)) {
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, "\tOpening task=%d", taskInfo.taskId)
+
+ // Remove from pending close tasks list if it's being opened again
+ pendingCloseTasks.removeIf { it.taskId == taskInfo.taskId }
+ // Move the task to the front of the visible tasks list
+ visibleTasks.removeIf { it.taskId == taskInfo.taskId }
+ visibleTasks.add(0, taskInfo)
+ notifyChanges = true
+ }
+ }
+
+ // TODO(346588978): We should verify the task list has actually changed before notifying
+ // (ie. starting an activity that's already top-most would result in no visible change)
+ if (notifyChanges) {
+ updateVisibleTasksList("transition-start")
+ }
+ }
+
+ private fun updateVisibleTasksList(reason: String) {
+ // This simply constructs a list of visible tasks, where the always-on-top tasks are moved
+ // to the front of the list in-order, to ensure that they match the visible z order
+ val orderedVisibleTasks = mutableListOf<RunningTaskInfo>()
+ var numAlwaysOnTop = 0
+ for (info in visibleTasks) {
+ if (info.windowingMode == WINDOWING_MODE_PINNED
+ || info.configuration.windowConfiguration.isAlwaysOnTop) {
+ orderedVisibleTasks.add(numAlwaysOnTop, info)
+ numAlwaysOnTop++
+ } else {
+ orderedVisibleTasks.add(info)
+ }
+ }
+ visibleTasks = orderedVisibleTasks
+
+ dumpVisibleTasks(reason)
+ notifyVisibleTasksChanged(visibleTasks)
}
override fun onTransitionReady(
@@ -56,26 +181,10 @@ class TaskStackTransitionObserver(
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction
) {
- if (DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue) {
- for (change in info.changes) {
- if (change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0) {
- continue
- }
-
- val taskInfo = change.taskInfo
- if (taskInfo == null || taskInfo.taskId == -1) {
- continue
- }
-
- // Find the first task that is opening, this should be the one at the front after
- // the transition
- if (TransitionUtil.isOpeningType(change.mode)) {
- notifyOnTaskMovedToFront(taskInfo)
- break
- } else if (change.mode == TRANSIT_CHANGE) {
- notifyOnTaskChanged(taskInfo)
- }
- }
+ if (enableShellTopTaskTracking()) {
+ onShellTopTaskTrackerFlagTransitionReady(info)
+ } else if (DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue) {
+ onDesktopOnlyFlagTransitionReady(info)
}
}
@@ -83,8 +192,35 @@ class TaskStackTransitionObserver(
override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
- override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {}
+ override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
+ if (enableShellTopTaskTracking()) {
+ if (pendingCloseTasks.isNotEmpty()) {
+ // Update the visible task list based on the pending close tasks
+ for (change in pendingCloseTasks) {
+ visibleTasks.removeIf {
+ it.taskId == change.taskId
+ }
+ }
+ updateVisibleTasksList("transition-finished")
+ }
+ }
+ }
+ override fun onTaskVanished(taskInfo: RunningTaskInfo?) {
+ if (!enableShellTopTaskTracking()) {
+ return
+ }
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, "Task vanished: task=%d", taskInfo?.taskId)
+ pendingCloseTasks.removeIf { it.taskId == taskInfo?.taskId }
+ if (visibleTasks.any { it.taskId == taskInfo?.taskId }) {
+ visibleTasks.removeIf { it.taskId == taskInfo?.taskId }
+ updateVisibleTasksList("task-vanished")
+ }
+ }
+
+ /**
+ * Adds a new task stack observer.
+ */
fun addTaskStackTransitionObserverListener(
taskStackTransitionObserverListener: TaskStackTransitionObserverListener,
executor: Executor
@@ -92,6 +228,9 @@ class TaskStackTransitionObserver(
taskStackTransitionObserverListeners[taskStackTransitionObserverListener] = executor
}
+ /**
+ * Removes an existing task stack observer.
+ */
fun removeTaskStackTransitionObserverListener(
taskStackTransitionObserverListener: TaskStackTransitionObserverListener
) {
@@ -99,22 +238,66 @@ class TaskStackTransitionObserver(
}
private fun notifyOnTaskMovedToFront(taskInfo: RunningTaskInfo) {
+ if (enableShellTopTaskTracking()) {
+ return
+ }
taskStackTransitionObserverListeners.forEach { (listener, executor) ->
executor.execute { listener.onTaskMovedToFrontThroughTransition(taskInfo) }
}
}
private fun notifyOnTaskChanged(taskInfo: RunningTaskInfo) {
+ if (enableShellTopTaskTracking()) {
+ return
+ }
taskStackTransitionObserverListeners.forEach { (listener, executor) ->
executor.execute { listener.onTaskChangedThroughTransition(taskInfo) }
}
}
+ private fun notifyVisibleTasksChanged(visibleTasks: List<RunningTaskInfo>) {
+ taskStackTransitionObserverListeners.forEach { (listener, executor) ->
+ executor.execute { listener.onVisibleTasksChanged(visibleTasks) }
+ }
+ }
+
+ fun dump(pw: PrintWriter, prefix: String) {
+ pw.println("${prefix}$TAG:")
+
+ if (visibleTasks.isEmpty()) {
+ pw.println("$prefix visibleTasks=[]")
+ } else {
+ val stringJoiner = StringJoiner(",\n\t", "[\n\t", "\n]")
+ visibleTasks.forEach {
+ stringJoiner.add("id=${it.taskId} cmp=${it.baseIntent.component}")
+ }
+ pw.println("$prefix visibleTasks=$stringJoiner")
+ }
+ }
+
+ /** Dumps the set of visible tasks to protolog */
+ private fun dumpVisibleTasks(reason: String) {
+ if (!WM_SHELL_TASK_OBSERVER.isEnabled) {
+ return
+ }
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, "\tVisible tasks (%s)", reason)
+ for (task in visibleTasks) {
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, "\t\ttaskId=%d package=%s", task.taskId,
+ task.baseIntent.component?.packageName)
+ }
+ }
+
/** Listener to use to get updates regarding task stack from this observer */
interface TaskStackTransitionObserverListener {
/** Called when a task is moved to front. */
fun onTaskMovedToFrontThroughTransition(taskInfo: RunningTaskInfo) {}
+ /** Called when the set of visible tasks have changed. */
+ fun onVisibleTasksChanged(visibleTasks: List<RunningTaskInfo>) {}
/** Called when a task info has changed. */
fun onTaskChangedThroughTransition(taskInfo: RunningTaskInfo) {}
}
+
+ companion object {
+ const val TAG = "TaskStackTransitionObserver"
+ }
}
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 cc0e1df115c2..7d1ffb80735e 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
@@ -1362,7 +1362,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
void clearSplitPairedInRecents(@ExitReason int exitReason) {
- if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return;
+ if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearSplitPairedInRecents: skipping reason=%s",
+ !mShouldUpdateRecents ? "shouldn't update" : exitReasonToString(exitReason));
+ return;
+ }
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearSplitPairedInRecents: reason=%s",
exitReasonToString(exitReason));
@@ -1608,6 +1612,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void updateRecentTasksSplitPair() {
// Preventing from single task update while processing recents.
if (!mShouldUpdateRecents || !mPausingTasks.isEmpty()) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "updateRecentTasksSplitPair: skipping reason=%s",
+ !mShouldUpdateRecents ? "shouldn't update" : "no pausing tasks");
return;
}
mRecentTasks.ifPresent(recentTasks -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index cdcf14e0cbf3..9cb9d2594878 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -81,7 +81,6 @@ import android.window.TaskSnapshot;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
import com.android.window.flags.Flags;
@@ -1008,8 +1007,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
relayoutParams.mWindowDecorConfig = windowDecorConfig;
if (DesktopModeStatus.useRoundedCorners()) {
- relayoutParams.mCornerRadius =
- (int) ScreenDecorationsUtils.getWindowCornerRadius(context);
+ relayoutParams.mCornerRadius = taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ ? loadDimensionPixelSize(context.getResources(),
+ R.dimen.desktop_windowing_freeform_rounded_corner_radius)
+ : INVALID_CORNER_RADIUS;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index a3c75bf33cde..852eee5f6672 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -107,6 +107,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
static final int INPUT_SINK_Z_ORDER = -2;
/**
+ * Invalid corner radius that signifies that corner radius should not be set.
+ */
+ static final int INVALID_CORNER_RADIUS = -1;
+
+ /**
* System-wide context. Only used to create context with overridden configurations.
*/
final Context mContext;
@@ -449,20 +454,22 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
startT.show(mTaskSurface);
}
- if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- if (!DesktopModeStatus.isVeiledResizeEnabled()) {
- // When fluid resize is enabled, add a background to freeform tasks
- int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
- mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
- mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
- mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
- startT.setColor(mTaskSurface, mTmpColor);
- }
- startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
- finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+ if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && !DesktopModeStatus.isVeiledResizeEnabled()) {
+ // When fluid resize is enabled, add a background to freeform tasks
+ int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
+ mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
+ mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
+ mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
+ startT.setColor(mTaskSurface, mTmpColor);
} else if (!DesktopModeStatus.isVeiledResizeEnabled()) {
startT.unsetColor(mTaskSurface);
}
+
+ if (params.mCornerRadius != INVALID_CORNER_RADIUS) {
+ startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+ finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+ }
}
/**
@@ -593,13 +600,25 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
private Rect calculateBoundingRectLocal(@NonNull OccludingCaptionElement element,
int elementWidthPx, @NonNull Rect captionRect) {
+ final boolean isRtl =
+ mDecorWindowContext.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_RTL;
switch (element.mAlignment) {
case START -> {
- return new Rect(0, 0, elementWidthPx, captionRect.height());
+ if (isRtl) {
+ return new Rect(captionRect.width() - elementWidthPx, 0,
+ captionRect.width(), captionRect.height());
+ } else {
+ return new Rect(0, 0, elementWidthPx, captionRect.height());
+ }
}
case END -> {
- return new Rect(captionRect.width() - elementWidthPx, 0,
- captionRect.width(), captionRect.height());
+ if (isRtl) {
+ return new Rect(0, 0, elementWidthPx, captionRect.height());
+ } else {
+ return new Rect(captionRect.width() - elementWidthPx, 0,
+ captionRect.width(), captionRect.height());
+ }
}
}
throw new IllegalArgumentException("Unexpected alignment " + element.mAlignment);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 503ad92d4d71..5f25f42039ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -114,7 +114,7 @@ internal class AppHandleViewHolder(
// If handle is not in status bar region(i.e., bottom stage in vertical split),
// do not create an input layer
if (position.y >= SystemBarUtils.getStatusBarHeight(context)) return
- if (!isCaptionVisible && statusBarInputLayerExists) {
+ if (!isCaptionVisible) {
disposeStatusBarInputLayer()
return
}
@@ -227,6 +227,7 @@ internal class AppHandleViewHolder(
* is not visible.
*/
fun disposeStatusBarInputLayer() {
+ if (!statusBarInputLayerExists) return
statusBarInputLayerExists = false
handler.post {
statusBarInputLayer?.releaseView()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontLandscape.kt
new file mode 100644
index 000000000000..898964f0fdbc
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontLandscape.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.BRING_APPS_TO_FRONT
+import com.android.wm.shell.scenarios.BringDesktopAppsToFront
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Bring apps to front by clicking on the App Header.
+ *
+ * Assert that the app windows move to front.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class BringDesktopAppsToFrontLandscape : BringDesktopAppsToFront(rotation = ROTATION_90) {
+
+ @ExpectedScenarios(["BRING_APPS_TO_FRONT"])
+ @Test
+ override fun bringDesktopAppsToFront() = super.bringDesktopAppsToFront()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(BRING_APPS_TO_FRONT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontPortrait.kt
new file mode 100644
index 000000000000..b123d7d96129
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontPortrait.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.flicker
+
+import android.tools.Rotation.ROTATION_0
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.BRING_APPS_TO_FRONT
+import com.android.wm.shell.scenarios.BringDesktopAppsToFront
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Bring apps to front by clicking on the App Header.
+ *
+ * Assert that the app windows move to front.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class BringDesktopAppsToFrontPortrait : BringDesktopAppsToFront(rotation = ROTATION_0) {
+
+ @ExpectedScenarios(["BRING_APPS_TO_FRONT"])
+ @Test
+ override fun bringDesktopAppsToFront() = super.bringDesktopAppsToFront()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(BRING_APPS_TO_FRONT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index 4cddf31321d6..88dc5489b404 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -25,6 +25,7 @@ import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
import android.tools.flicker.assertors.assertions.AppWindowAlignsWithOnlyOneDisplayCornerAtEnd
import android.tools.flicker.assertors.assertions.AppWindowBecomesInvisible
+import android.tools.flicker.assertors.assertions.AppWindowBecomesTopWindow
import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
import android.tools.flicker.assertors.assertions.AppWindowCoversLeftHalfScreenAtEnd
import android.tools.flicker.assertors.assertions.AppWindowCoversRightHalfScreenAtEnd
@@ -345,6 +346,30 @@ class DesktopModeFlickerScenarios {
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
+ val BRING_APPS_TO_FRONT =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("BRING_APPS_TO_FRONT"),
+ extractor =
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return transitions.filter {
+ it.type == TransitionType.TO_FRONT
+ }
+ }
+ }
+ ),
+ assertions =
+ AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppWindowBecomesTopWindow(DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(DESKTOP_MODE_APP),
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+ )
+
val CASCADE_APP =
FlickerConfigEntry(
scenarioId = ScenarioId("CASCADE_APP"),
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWindowWithDragToTopDragZoneLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWindowWithDragToTopDragZoneLandscape.kt
new file mode 100644
index 000000000000..e1120bdda194
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWindowWithDragToTopDragZoneLandscape.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.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
+import com.android.wm.shell.scenarios.MaximizeAppWindowWithDragToTopDragZone
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize app window by dragging it to the top drag zone.
+ *
+ * Assert that the app window keeps the same increases in size, filling the vertical and horizontal
+ * stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppWindowWithDragToTopDragZoneLandscape : MaximizeAppWindowWithDragToTopDragZone(
+ rotation = ROTATION_90
+) {
+ @ExpectedScenarios(["MAXIMIZE_APP"])
+ @Test
+ override fun maximizeAppWithDragToTopDragZone() = super.maximizeAppWithDragToTopDragZone()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWindowWithDragToTopDragZonePortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWindowWithDragToTopDragZonePortrait.kt
new file mode 100644
index 000000000000..fb910c7b907d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWindowWithDragToTopDragZonePortrait.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
+import com.android.wm.shell.scenarios.MaximizeAppWindowWithDragToTopDragZone
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize app window by dragging it to the top drag zone.
+ *
+ * Assert that the app window keeps the same increases in size, filling the vertical and horizontal
+ * stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppWindowWithDragToTopDragZonePortrait : MaximizeAppWindowWithDragToTopDragZone() {
+ @ExpectedScenarios(["MAXIMIZE_APP"])
+ @Test
+ override fun maximizeAppWithDragToTopDragZone() = super.maximizeAppWithDragToTopDragZone()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/BringDesktopAppsToFrontTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/BringDesktopAppsToFrontTest.kt
new file mode 100644
index 000000000000..6c8cc68da7c0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/BringDesktopAppsToFrontTest.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.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.BringDesktopAppsToFront
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [BringDesktopAppsToFront]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class BringDesktopAppsToFrontTest : BringDesktopAppsToFront()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/BringDesktopAppsToFront.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/BringDesktopAppsToFront.kt
new file mode 100644
index 000000000000..1db22ebb8c73
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/BringDesktopAppsToFront.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.scenarios
+
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Test Base Class")
+abstract class BringDesktopAppsToFront(
+ val rotation: Rotation = Rotation.ROTATION_0,
+ isResizable: Boolean = true,
+ isLandscapeApp: Boolean = true,
+) : DesktopScenarioCustomAppTestBase(isResizable, isLandscapeApp) {
+
+ private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+
+ @Rule
+ @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ tapl.enableTransientTaskbar(false)
+ // Launch a first app and snap it to left side so that it doesn't overlap too much with
+ // the next launching app, and their headers are visible enough to switch focus by tapping
+ // on them.
+ testApp.enterDesktopMode(wmHelper, device)
+ testApp.snapResizeDesktopApp(wmHelper, device, instrumentation.context, toLeft = true)
+ mailApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun bringDesktopAppsToFront() {
+ testApp.bringToFront(wmHelper, device)
+ mailApp.bringToFront(wmHelper, device)
+ testApp.bringToFront(wmHelper, device)
+ mailApp.bringToFront(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ mailApp.exit(wmHelper)
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt
index 351a70094654..f9bf49ecae1c 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt
@@ -57,7 +57,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
mailApp.launchViaIntent(wmHelper)
nonResizeableApp.launchViaIntent(wmHelper)
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt
index 3f9927f1fab6..16e537361b66 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt
@@ -40,7 +40,7 @@ abstract class DragAppWindowMultiWindow : DragAppWindowScenarioTestBase()
@Before
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
mailApp.launchViaIntent(wmHelper)
newTasksApp.launchViaIntent(wmHelper)
imeApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
index 6d52a11153d9..c43a57594fb3 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
@@ -47,7 +47,7 @@ open class DragAppWindowMultiWindowAndPip : DragAppWindowScenarioTestBase()
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
// Set string extra to ensure the app is on PiP mode at launch
pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = mapOf("enter_pip" to "true"))
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
mailApp.launchViaIntent(wmHelper)
newTasksApp.launchViaIntent(wmHelper)
imeApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowSingleWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowSingleWindow.kt
index 91cfd17340fc..786a8b710434 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowSingleWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowSingleWindow.kt
@@ -36,7 +36,7 @@ open class DragAppWindowSingleWindow : DragAppWindowScenarioTestBase()
@Before
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
index 967bd29958c2..0f546cdf97c5 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
@@ -49,7 +49,7 @@ constructor(
@Test
open fun enterDesktopWithDrag() {
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopModeWithDrag(wmHelper, device)
}
@After
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
index f442fdb31592..28008393da84 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
@@ -46,7 +46,7 @@ constructor(
instrumentation.context.resources.getBoolean(R.bool.config_dragToMaximizeInDesktopMode))
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximiseAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximiseAppWithCornerResize.kt
index 6637b01f9d9c..5cf51e3be18c 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximiseAppWithCornerResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximiseAppWithCornerResize.kt
@@ -66,7 +66,7 @@ abstract class MaximiseAppWithCornerResize(
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
ChangeDisplayOrientationRule.setRotation(rotation)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
testApp.cornerResize(
wmHelper,
device,
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
index a54d497bf511..d2be4944d365 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
@@ -58,7 +58,7 @@ constructor(private val rotation: Rotation = Rotation.ROTATION_0, isResizable: B
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
ChangeDisplayOrientationRule.setRotation(rotation)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt
index a2b88f278ff2..60a0fb547909 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt
@@ -59,7 +59,7 @@ constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
ChangeDisplayOrientationRule.setRotation(rotation)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
index b5483634b057..971637b62604 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
@@ -61,7 +61,7 @@ constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
ChangeDisplayOrientationRule.setRotation(rotation)
- testApp1.enterDesktopWithDrag(wmHelper, device)
+ testApp1.enterDesktopMode(wmHelper, device)
testApp2.launchViaIntent(wmHelper)
testApp3.launchViaIntent(wmHelper)
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt
index b86765e23422..7987f7ec59fa 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt
@@ -58,7 +58,7 @@ open class MinimizeWindowOnAppOpen()
@Before
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
mailApp.launchViaIntent(wmHelper)
newTasksApp.launchViaIntent(wmHelper)
imeApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt
index aad266fb8374..6ce36f53f0d1 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt
@@ -61,7 +61,7 @@ abstract class OpenAppsInDesktopMode(val rotation: Rotation = Rotation.ROTATION_
tapl.setExpectedRotation(rotation.value)
tapl.enableTransientTaskbar(false)
ChangeDisplayOrientationRule.setRotation(rotation)
- firstApp.enterDesktopWithDrag(wmHelper, device)
+ firstApp.enterDesktopMode(wmHelper, device)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
index bfee3181cbc0..eefa0bb3c00a 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
@@ -61,7 +61,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0,
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
mailApp.launchViaIntent(wmHelper)
newTasksApp.launchViaIntent(wmHelper)
imeApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
index 5b1b64e7c562..0226eb35de14 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
@@ -65,7 +65,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0,
tapl.setExpectedRotation(rotation.value)
// Set string extra to ensure the app is on PiP mode at launch
pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = mapOf("enter_pip" to "true"))
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
mailApp.launchViaIntent(wmHelper)
newTasksApp.launchViaIntent(wmHelper)
imeApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
index a7cebf402d8e..64636230e5e0 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
@@ -67,7 +67,7 @@ abstract class ResizeAppWithCornerResize(
tapl.setEnableRotation(true)
ChangeDisplayOrientationRule.setRotation(rotation)
tapl.setExpectedRotation(rotation.value)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt
index 67802387b267..f198cfed7c50 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt
@@ -60,7 +60,7 @@ constructor(
)
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
index 2b40497844ef..fd4c2434589d 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
@@ -56,7 +56,7 @@ constructor(private val toLeft: Boolean = true, isResizable: Boolean = true) {
@Before
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
index b4bd7e1c5211..62e860ed24a7 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
@@ -56,7 +56,7 @@ constructor(private val toLeft: Boolean = true, isResizable: Boolean = true) {
@Before
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt
index f08e50e0d4ee..de330e072ad1 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt
@@ -59,7 +59,7 @@ open class StartAppMediaProjectionResizeAndDrag {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
tapl.setEnableRotation(true)
tapl.setExpectedRotation(0)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
index ce235d445fe5..4b3f15f1db86 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
@@ -67,7 +67,7 @@ open class StartAppMediaProjectionWithMaxDesktopWindows {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
tapl.setEnableRotation(true)
tapl.setExpectedRotation(0)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt
index 005195296c62..a1083671f687 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt
@@ -62,7 +62,7 @@ open class StartScreenMediaProjectionWithMaxDesktopWindows {
@Before
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt
index dad2eb633c72..1455bd1888e2 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt
@@ -54,7 +54,7 @@ constructor(val navigationMode: NavBar = NavBar.MODE_GESTURAL) {
@Before
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
- testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestSyncExecutor.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestSyncExecutor.kt
new file mode 100644
index 000000000000..528ca7e94ee3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestSyncExecutor.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell
+
+import com.android.wm.shell.common.ShellExecutor
+
+/**
+ * Test ShellExecutor that runs everything synchronously.
+ */
+class TestSyncExecutor : ShellExecutor {
+ override fun execute(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun executeDelayed(runnable: Runnable, delayMillis: Long) {
+ runnable.run()
+ }
+
+ override fun removeCallbacks(runnable: Runnable) {
+ }
+
+ override fun hasCallback(runnable: Runnable): Boolean {
+ return false
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index f21f26443748..62717a32d99f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -31,6 +31,8 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TransitionType
@@ -47,6 +49,7 @@ import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler.PendingMix
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.StubTransaction
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
@@ -491,6 +494,72 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun startLaunchTransition_unknownLaunchingTask_animates() {
+ val wct = WindowContainerTransaction()
+ val task = createTask(WINDOWING_MODE_FREEFORM)
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(transition)
+ whenever(transitions.dispatchTransition(eq(transition), any(), any(), any(), any(), any()))
+ .thenReturn(mock())
+
+ mixedHandler.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ taskId = null,
+ )
+
+ val started = mixedHandler.startAnimation(
+ transition,
+ createTransitionInfo(
+ TRANSIT_OPEN,
+ listOf(createChange(task, mode = TRANSIT_OPEN))
+ ),
+ StubTransaction(),
+ StubTransaction(),
+ ) { }
+
+ assertThat(started).isEqualTo(true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun startLaunchTransition_unknownLaunchingTaskOverImmersive_animatesImmersiveChange() {
+ val wct = WindowContainerTransaction()
+ val immersiveTask = createTask(WINDOWING_MODE_FREEFORM)
+ val openingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(transition)
+ whenever(transitions.dispatchTransition(eq(transition), any(), any(), any(), any(), any()))
+ .thenReturn(mock())
+
+ mixedHandler.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ taskId = null,
+ exitingImmersiveTask = immersiveTask.taskId,
+ )
+
+ val immersiveChange = createChange(immersiveTask, mode = TRANSIT_CHANGE)
+ val openingChange = createChange(openingTask, mode = TRANSIT_OPEN)
+ val started = mixedHandler.startAnimation(
+ transition,
+ createTransitionInfo(
+ TRANSIT_OPEN,
+ listOf(immersiveChange, openingChange)
+ ),
+ StubTransaction(),
+ StubTransaction(),
+ ) { }
+
+ assertThat(started).isEqualTo(true)
+ verify(desktopImmersiveController)
+ .animateResizeChange(eq(immersiveChange), any(), any(), any())
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
val wct = WindowContainerTransaction()
@@ -712,9 +781,13 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
changes.forEach { change -> addChange(change) }
}
- private fun createChange(task: RunningTaskInfo): TransitionInfo.Change =
+ private fun createChange(
+ task: RunningTaskInfo,
+ @TransitionInfo.TransitionMode mode: Int = TRANSIT_NONE
+ ): TransitionInfo.Change =
TransitionInfo.Change(task.token, SurfaceControl()).apply {
taskInfo = task
+ setMode(mode)
}
private fun createTask(@WindowingMode windowingMode: Int): RunningTaskInfo =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index ad266ead774e..2319716617bf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -100,6 +100,7 @@ import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTasksController.TaskbarDesktopTaskListener
@@ -167,6 +168,7 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.times
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
import org.mockito.kotlin.eq
@@ -294,10 +296,10 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
whenever(mMockDesktopImmersiveController
.exitImmersiveIfApplicable(any(), any<RunningTaskInfo>()))
- .thenReturn(DesktopImmersiveController.ExitResult.NoExit)
+ .thenReturn(ExitResult.NoExit)
whenever(mMockDesktopImmersiveController
.exitImmersiveIfApplicable(any(), anyInt(), anyOrNull()))
- .thenReturn(DesktopImmersiveController.ExitResult.NoExit)
+ .thenReturn(ExitResult.NoExit)
controller = createController()
controller.setSplitScreenController(splitScreenController)
@@ -1833,7 +1835,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
.thenReturn(transition)
whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task)))
- .thenReturn(DesktopImmersiveController.ExitResult.Exit(
+ .thenReturn(
+ ExitResult.Exit(
exitingTask = task.taskId,
runOnTransitionStart = runOnTransit,
))
@@ -3214,13 +3217,43 @@ class DesktopTasksControllerTest : ShellTestCase() {
fun newWindow_fromFreeformAddsNewWindow() {
setUpLandscapeDisplay()
val task = setUpFreeformTask()
- val wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val wctCaptor = argumentCaptor<WindowContainerTransaction>()
+ val transition = Binder()
+ whenever(mMockDesktopImmersiveController
+ .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull()))
+ .thenReturn(ExitResult.NoExit)
+ whenever(desktopMixedTransitionHandler
+ .startLaunchTransition(anyInt(), any(), anyOrNull(), anyOrNull(), anyOrNull()))
+ .thenReturn(transition)
+
runOpenNewWindow(task)
- verify(transitions).startTransition(anyInt(), wctCaptor.capture(), anyOrNull())
- assertThat(ActivityOptions.fromBundle(wctCaptor.value.hierarchyOps[0].launchOptions)
+
+ verify(desktopMixedTransitionHandler)
+ .startLaunchTransition(anyInt(), wctCaptor.capture(), anyOrNull(), anyOrNull(), anyOrNull())
+ assertThat(ActivityOptions.fromBundle(wctCaptor.firstValue.hierarchyOps[0].launchOptions)
.launchWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun newWindow_fromFreeform_exitsImmersiveIfNeeded() {
+ setUpLandscapeDisplay()
+ val immersiveTask = setUpFreeformTask()
+ val task = setUpFreeformTask()
+ val runOnStart = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(mMockDesktopImmersiveController
+ .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull()))
+ .thenReturn(ExitResult.Exit(immersiveTask.taskId, runOnStart))
+ whenever(desktopMixedTransitionHandler
+ .startLaunchTransition(anyInt(), any(), anyOrNull(), anyOrNull(), anyOrNull()))
+ .thenReturn(transition)
+
+ runOpenNewWindow(task)
+
+ runOnStart.assertOnlyInvocation(transition)
+ }
+
private fun runOpenNewWindow(task: RunningTaskInfo) {
markTaskVisible(task)
task.baseActivity = mock(ComponentName::class.java)
@@ -3314,7 +3347,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
.thenReturn(transition)
whenever(mMockDesktopImmersiveController
.exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId), eq(freeformTask.taskId)))
- .thenReturn(DesktopImmersiveController.ExitResult.Exit(
+ .thenReturn(
+ ExitResult.Exit(
exitingTask = immersiveTask.taskId,
runOnTransitionStart = runOnStartTransit,
))
@@ -3719,7 +3753,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
val transition = Binder()
whenever(mMockDesktopImmersiveController
.exitImmersiveIfApplicable(wct, task.displayId, task.taskId))
- .thenReturn(DesktopImmersiveController.ExitResult.Exit(
+ .thenReturn(
+ ExitResult.Exit(
exitingTask = 5,
runOnTransitionStart = runOnStartTransit,
))
@@ -3740,7 +3775,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
val transition = Binder()
whenever(mMockDesktopImmersiveController
.exitImmersiveIfApplicable(wct, task.displayId, task.taskId))
- .thenReturn(DesktopImmersiveController.ExitResult.Exit(
+ .thenReturn(
+ ExitResult.Exit(
exitingTask = 5,
runOnTransitionStart = runOnStartTransit,
))
@@ -3760,7 +3796,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
val transition = Binder()
whenever(mMockDesktopImmersiveController
.exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId)))
- .thenReturn(DesktopImmersiveController.ExitResult.Exit(
+ .thenReturn(
+ ExitResult.Exit(
exitingTask = 5,
runOnTransitionStart = runOnStartTransit,
))
@@ -3782,7 +3819,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
val transition = Binder()
whenever(mMockDesktopImmersiveController
.exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId)))
- .thenReturn(DesktopImmersiveController.ExitResult.Exit(
+ .thenReturn(
+ ExitResult.Exit(
exitingTask = 5,
runOnTransitionStart = runOnStartTransit,
))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
index a4008c1e6995..72c466663a56 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -39,6 +40,7 @@ import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.wm.shell.R;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.phone.PipAppIconOverlay;
@@ -49,33 +51,25 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
- * Unit test again {@link PipEnterAnimator}.
+ * Unit test against {@link PipEnterAnimator}.
*/
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
public class PipEnterAnimatorTest {
+ private static final float TEST_CORNER_RADIUS = 1f;
+ private static final float TEST_SHADOW_RADIUS = 2f;
@Mock private Context mMockContext;
-
@Mock private Resources mMockResources;
-
@Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
-
@Mock private SurfaceControl.Transaction mMockAnimateTransaction;
-
@Mock private SurfaceControl.Transaction mMockStartTransaction;
-
@Mock private SurfaceControl.Transaction mMockFinishTransaction;
-
@Mock private Runnable mMockStartCallback;
-
@Mock private Runnable mMockEndCallback;
-
@Mock private PipAppIconOverlay mMockPipAppIconOverlay;
-
@Mock private SurfaceControl mMockAppIconOverlayLeash;
-
@Mock private ActivityInfo mMockActivityInfo;
@Surface.Rotation private int mRotation;
@@ -89,13 +83,15 @@ public class PipEnterAnimatorTest {
when(mMockContext.getResources()).thenReturn(mMockResources);
when(mMockResources.getInteger(anyInt())).thenReturn(0);
when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction);
- when(mMockAnimateTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
- .thenReturn(mMockAnimateTransaction);
- when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
- .thenReturn(mMockStartTransaction);
- when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
- .thenReturn(mMockFinishTransaction);
when(mMockPipAppIconOverlay.getLeash()).thenReturn(mMockAppIconOverlayLeash);
+ when(mMockResources.getDimensionPixelSize(R.dimen.pip_corner_radius))
+ .thenReturn((int) TEST_CORNER_RADIUS);
+ when(mMockResources.getDimensionPixelSize(R.dimen.pip_shadow_radius))
+ .thenReturn((int) TEST_SHADOW_RADIUS);
+
+ prepareTransaction(mMockAnimateTransaction);
+ prepareTransaction(mMockStartTransaction);
+ prepareTransaction(mMockFinishTransaction);
mTestLeash = new SurfaceControl.Builder()
.setContainerLayer()
@@ -122,6 +118,12 @@ public class PipEnterAnimatorTest {
verify(mMockStartCallback).run();
verifyZeroInteractions(mMockEndCallback);
+
+ // Check corner and shadow radii were set
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
}
@Test
@@ -142,6 +144,12 @@ public class PipEnterAnimatorTest {
verify(mMockStartCallback).run();
verify(mMockEndCallback).run();
+
+ // Check corner and shadow radii were set
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
}
@Test
@@ -197,5 +205,21 @@ public class PipEnterAnimatorTest {
verify(mMockPipAppIconOverlay).onAnimationUpdate(
eq(mMockAnimateTransaction), anyFloat(), eq(fraction), eq(mEndBounds));
+
+ // Check corner and shadow radii were set
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+ }
+
+ // set up transaction chaining
+ private void prepareTransaction(SurfaceControl.Transaction tx) {
+ when(tx.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(tx);
+ when(tx.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(tx);
+ when(tx.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(tx);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
index 0adb50b81896..23fbad05ec99 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
@@ -16,15 +16,18 @@
package com.android.wm.shell.pip2.animation;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.kotlin.MatchersKt.eq;
-import static org.junit.Assert.assertEquals;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
@@ -34,12 +37,14 @@ import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.wm.shell.R;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -52,40 +57,40 @@ import org.mockito.MockitoAnnotations;
public class PipResizeAnimatorTest {
private static final float FLOAT_COMPARISON_DELTA = 0.001f;
+ private static final float TEST_CORNER_RADIUS = 1f;
+ private static final float TEST_SHADOW_RADIUS = 2f;
@Mock private Context mMockContext;
-
+ @Mock private Resources mMockResources;
@Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
-
@Mock private SurfaceControl.Transaction mMockTransaction;
-
@Mock private SurfaceControl.Transaction mMockStartTransaction;
-
@Mock private SurfaceControl.Transaction mMockFinishTransaction;
-
@Mock private Runnable mMockStartCallback;
-
@Mock private Runnable mMockEndCallback;
+ @Captor private ArgumentCaptor<Matrix> mArgumentCaptor;
+
private PipResizeAnimator mPipResizeAnimator;
private Rect mBaseBounds;
private Rect mStartBounds;
private Rect mEndBounds;
private SurfaceControl mTestLeash;
- private ArgumentCaptor<Matrix> mArgumentCaptor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
- when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
- .thenReturn(mMockTransaction);
- when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
- .thenReturn(mMockStartTransaction);
- when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
- .thenReturn(mMockFinishTransaction);
-
- mArgumentCaptor = ArgumentCaptor.forClass(Matrix.class);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getDimensionPixelSize(R.dimen.pip_corner_radius))
+ .thenReturn((int) TEST_CORNER_RADIUS);
+ when(mMockResources.getDimensionPixelSize(R.dimen.pip_shadow_radius))
+ .thenReturn((int) TEST_SHADOW_RADIUS);
+
+ prepareTransaction(mMockTransaction);
+ prepareTransaction(mMockStartTransaction);
+ prepareTransaction(mMockFinishTransaction);
+
mTestLeash = new SurfaceControl.Builder()
.setContainerLayer()
.setName("PipResizeAnimatorTest")
@@ -187,6 +192,12 @@ public class PipResizeAnimatorTest {
assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+
+ // Check corner and shadow radii were set
+ verify(mMockTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
}
@Test
@@ -237,6 +248,12 @@ public class PipResizeAnimatorTest {
assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+
+ // Check corner and shadow radii were set
+ verify(mMockTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
}
@Test
@@ -272,5 +289,21 @@ public class PipResizeAnimatorTest {
mArgumentCaptor.getValue().getValues(matrix);
assertEquals(matrix[Matrix.MSKEW_X], 0f, FLOAT_COMPARISON_DELTA);
assertEquals(matrix[Matrix.MSKEW_Y], 0f, FLOAT_COMPARISON_DELTA);
+
+ // Check corner and shadow radii were set
+ verify(mMockTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+ }
+
+ // set up transaction chaining
+ private void prepareTransaction(SurfaceControl.Transaction tx) {
+ when(tx.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(tx);
+ when(tx.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(tx);
+ when(tx.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(tx);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
index 2b30bc360d06..fd3adabfd44b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
@@ -176,6 +176,30 @@ class GroupedTaskInfoTest : ShellTestCase() {
assertThat(recentTaskInfoParcel.minimizedTaskIds).isEqualTo(arrayOf(2).toIntArray())
}
+ @Test
+ fun testGetTaskById_singleTasks() {
+ val task1 = createTaskInfo(id = 1234)
+
+ val taskInfo = GroupedTaskInfo.forFullscreenTasks(task1)
+
+ assertThat(taskInfo.getTaskById(1234)).isEqualTo(task1)
+ assertThat(taskInfo.containsTask(1234)).isTrue()
+ }
+
+ @Test
+ fun testGetTaskById_multipleTasks() {
+ val task1 = createTaskInfo(id = 1)
+ val task2 = createTaskInfo(id = 2)
+ val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50)
+
+ val taskInfo = GroupedTaskInfo.forSplitTasks(task1, task2, splitBounds)
+
+ assertThat(taskInfo.getTaskById(1)).isEqualTo(task1)
+ assertThat(taskInfo.getTaskById(2)).isEqualTo(task2)
+ assertThat(taskInfo.containsTask(1)).isTrue()
+ assertThat(taskInfo.containsTask(2)).isTrue()
+ }
+
private fun createTaskInfo(id: Int) = ActivityManager.RecentTaskInfo().apply {
taskId = id
token = WindowContainerToken(mock(IWindowContainerToken::class.java))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index dede583ca970..12c397868f5a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -610,7 +610,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo);
GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo);
- verify(mRecentTasksListener).onTaskMovedToFront(eq(new GroupedTaskInfo[] { runningTask }));
+ verify(mRecentTasksListener).onTaskMovedToFront(eq(runningTask));
}
@Test
@@ -656,6 +656,35 @@ public class RecentTasksControllerTest extends ShellTestCase {
assertEquals(splitBounds4, pair2Bounds);
}
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void shellTopTaskTracker_onTaskStackChanged_expectNoRecentsChanged() throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ mRecentTasksControllerReal.onTaskStackChanged();
+ verify(mRecentTasksListener, never()).onRecentTasksChanged();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void shellTopTaskTracker_onTaskRemoved_expectNoRecentsChanged() throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ mRecentTasksControllerReal.onTaskRemoved(taskInfo);
+ verify(mRecentTasksListener, never()).onRecentTasksChanged();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void shellTopTaskTracker_onVisibleTasksChanged() throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ mRecentTasksControllerReal.onVisibleTasksChanged(List.of(taskInfo));
+ verify(mRecentTasksListener, never()).onVisibleTasksChanged(any());
+ }
+
/**
* Helper to create a task with a given task id.
*/
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
index efe4fb18f273..99194620c313 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
@@ -16,21 +16,36 @@
package com.android.wm.shell.recents
-import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.TaskInfo
import android.app.WindowConfiguration
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.content.ComponentName
+import android.content.Intent
import android.os.IBinder
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl
import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.view.WindowManager.TRANSIT_FIRST_CUSTOM
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.IWindowContainerToken
import android.window.TransitionInfo
+import android.window.TransitionInfo.FLAG_MOVED_TO_TOP
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.TestSyncExecutor
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
@@ -61,7 +76,10 @@ class TaskStackTransitionObserverTest {
@JvmField @Rule val setFlagsRule = SetFlagsRule()
@Mock private lateinit var shellInit: ShellInit
- @Mock lateinit var testExecutor: ShellExecutor
+ @Mock private lateinit var shellTaskOrganizerLazy: Lazy<ShellTaskOrganizer>
+ @Mock private lateinit var shellTaskOrganizer: ShellTaskOrganizer
+ @Mock private lateinit var shellCommandHandler: ShellCommandHandler
+ @Mock private lateinit var testExecutor: ShellExecutor
@Mock private lateinit var transitionsLazy: Lazy<Transitions>
@Mock private lateinit var transitions: Transitions
@Mock private lateinit var mockTransitionBinder: IBinder
@@ -73,24 +91,23 @@ class TaskStackTransitionObserverTest {
MockitoAnnotations.initMocks(this)
shellInit = Mockito.spy(ShellInit(testExecutor))
whenever(transitionsLazy.get()).thenReturn(transitions)
- transitionObserver = TaskStackTransitionObserver(transitionsLazy, shellInit)
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(shellInit)
- .addInitCallback(initRunnableCaptor.capture(), same(transitionObserver))
- initRunnableCaptor.value.run()
- } else {
- transitionObserver.onInit()
- }
+ whenever(shellTaskOrganizerLazy.get()).thenReturn(shellTaskOrganizer)
+ transitionObserver = TaskStackTransitionObserver(shellInit, shellTaskOrganizerLazy,
+ shellCommandHandler, transitionsLazy)
+
+ val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(shellInit)
+ .addInitCallback(initRunnableCaptor.capture(), same(transitionObserver))
+ initRunnableCaptor.value.run()
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
fun testRegistersObserverAtInit() {
verify(transitions).registerObserver(same(transitionObserver))
}
@Test
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
@EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
fun taskCreated_freeformWindow_listenerNotified() {
val listener = TestListener()
@@ -98,11 +115,11 @@ class TaskStackTransitionObserverTest {
transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
val change =
createChange(
- WindowManager.TRANSIT_OPEN,
+ TRANSIT_OPEN,
createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
)
val transitionInfo =
- TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build()
+ TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
callOnTransitionFinished()
@@ -114,6 +131,7 @@ class TaskStackTransitionObserverTest {
}
@Test
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
@EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
fun taskCreated_fullscreenWindow_listenerNotified() {
val listener = TestListener()
@@ -121,11 +139,11 @@ class TaskStackTransitionObserverTest {
transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
val change =
createChange(
- WindowManager.TRANSIT_OPEN,
- createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+ TRANSIT_OPEN,
+ createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)
)
val transitionInfo =
- TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build()
+ TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
callOnTransitionFinished()
@@ -133,10 +151,11 @@ class TaskStackTransitionObserverTest {
assertThat(listener.taskInfoOnTaskMovedToFront.taskId).isEqualTo(1)
assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
- .isEqualTo(WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
}
@Test
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
@EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
fun taskCreated_freeformWindowOnTopOfFreeform_listenerNotified() {
val listener = TestListener()
@@ -144,7 +163,7 @@ class TaskStackTransitionObserverTest {
transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
val freeformOpenChange =
createChange(
- WindowManager.TRANSIT_OPEN,
+ TRANSIT_OPEN,
createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
)
val freeformReorderChange =
@@ -153,7 +172,7 @@ class TaskStackTransitionObserverTest {
createTaskInfo(2, WindowConfiguration.WINDOWING_MODE_FREEFORM)
)
val transitionInfo =
- TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0)
+ TransitionInfoBuilder(TRANSIT_OPEN, 0)
.addChange(freeformOpenChange)
.addChange(freeformReorderChange)
.build()
@@ -169,6 +188,7 @@ class TaskStackTransitionObserverTest {
}
@Test
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
@EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
fun transitionMerged_withChange_onlyOpenChangeIsNotified() {
val listener = TestListener()
@@ -178,11 +198,11 @@ class TaskStackTransitionObserverTest {
// Create open transition
val change =
createChange(
- WindowManager.TRANSIT_OPEN,
+ TRANSIT_OPEN,
createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
)
val transitionInfo =
- TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build()
+ TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
// create change transition to be merged to above transition
val mergedChange =
@@ -212,6 +232,7 @@ class TaskStackTransitionObserverTest {
}
@Test
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
@EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
fun transitionMerged_withOpen_lastOpenChangeIsNotified() {
val listener = TestListener()
@@ -221,20 +242,20 @@ class TaskStackTransitionObserverTest {
// Create open transition
val change =
createChange(
- WindowManager.TRANSIT_OPEN,
+ TRANSIT_OPEN,
createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
)
val transitionInfo =
- TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build()
+ TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
// create change transition to be merged to above transition
val mergedChange =
createChange(
- WindowManager.TRANSIT_OPEN,
+ TRANSIT_OPEN,
createTaskInfo(2, WindowConfiguration.WINDOWING_MODE_FREEFORM)
)
val mergedTransitionInfo =
- TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(mergedChange).build()
+ TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(mergedChange).build()
val mergedTransition = Mockito.mock(IBinder::class.java)
callOnTransitionReady(transitionInfo)
@@ -250,6 +271,7 @@ class TaskStackTransitionObserverTest {
}
@Test
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
@EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
fun taskChange_freeformWindowToFullscreenWindow_listenerNotified() {
val listener = TestListener()
@@ -257,11 +279,11 @@ class TaskStackTransitionObserverTest {
transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
val freeformState =
createChange(
- WindowManager.TRANSIT_OPEN,
+ TRANSIT_OPEN,
createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
)
val transitionInfoOpen =
- TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(freeformState).build()
+ TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(freeformState).build()
callOnTransitionReady(transitionInfoOpen)
callOnTransitionFinished()
executor.flushAll()
@@ -276,7 +298,7 @@ class TaskStackTransitionObserverTest {
val fullscreenState =
createChange(
WindowManager.TRANSIT_CHANGE,
- createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+ createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)
)
val transitionInfoChange =
TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0)
@@ -301,6 +323,7 @@ class TaskStackTransitionObserverTest {
}
@Test
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
@EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
fun singleTransition_withOpenAndChange_onlyOpenIsNotified() {
val listener = TestListener()
@@ -310,13 +333,13 @@ class TaskStackTransitionObserverTest {
// Creating multiple changes to be fired in a single transition
val freeformState =
createChange(
- mode = WindowManager.TRANSIT_OPEN,
+ mode = TRANSIT_OPEN,
taskInfo = createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
)
val fullscreenState =
createChange(
mode = WindowManager.TRANSIT_CHANGE,
- taskInfo = createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+ taskInfo = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)
)
val transitionInfoWithChanges =
@@ -336,6 +359,7 @@ class TaskStackTransitionObserverTest {
}
@Test
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
@EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
fun singleTransition_withMultipleChanges_listenerNotified_forEachChange() {
val listener = TestListener()
@@ -349,7 +373,7 @@ class TaskStackTransitionObserverTest {
listOf(
WindowConfiguration.WINDOWING_MODE_FREEFORM,
WindowConfiguration.WINDOW_CONFIG_DISPLAY_ROTATION,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+ WINDOWING_MODE_FULLSCREEN
)
.map { change ->
createChange(
@@ -376,19 +400,259 @@ class TaskStackTransitionObserverTest {
}
}
- class TestListener : TaskStackTransitionObserver.TaskStackTransitionObserverListener {
- var taskInfoOnTaskMovedToFront = ActivityManager.RunningTaskInfo()
- var taskInfoOnTaskChanged = mutableListOf<ActivityManager.RunningTaskInfo>()
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ fun openTransition_visibleTasksChanged() {
+ val listener = TestListener()
+ val executor = TestSyncExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+ // Model an opening task
+ val firstOpeningTransition =
+ createTransitionInfo(TRANSIT_OPEN,
+ listOf(
+ createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FULLSCREEN),
+ )
+ )
+
+ callOnTransitionReady(firstOpeningTransition)
+ callOnTransitionFinished()
+ // Assert that the task is reported visible
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(1)
+ assertVisibleTasks(listener, listOf(1))
+
+ // Model opening another task
+ val nextOpeningTransition =
+ createTransitionInfo(TRANSIT_OPEN,
+ listOf(
+ createChange(TRANSIT_OPEN, 2, WINDOWING_MODE_FULLSCREEN),
+ createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FULLSCREEN),
+ )
+ )
+
+ callOnTransitionReady(nextOpeningTransition)
+ // Assert that the visible list from top to bottom is valid (opening, closing)
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(2)
+ assertVisibleTasks(listener, listOf(2, 1))
+
+ callOnTransitionFinished()
+ // Assert that after the transition finishes, there is only the opening task remaining
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(3)
+ assertVisibleTasks(listener, listOf(2))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ fun toFrontTransition_visibleTasksChanged() {
+ val listener = TestListener()
+ val executor = TestSyncExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+ // Model an opening task
+ val firstOpeningTransition =
+ createTransitionInfo(TRANSIT_OPEN,
+ listOf(
+ createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FULLSCREEN),
+ )
+ )
+
+ callOnTransitionReady(firstOpeningTransition)
+ callOnTransitionFinished()
+ // Assert that the task is reported visible
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(1)
+ assertVisibleTasks(listener, listOf(1))
+
+ // Model opening another task
+ val nextOpeningTransition =
+ createTransitionInfo(TRANSIT_OPEN,
+ listOf(
+ createChange(TRANSIT_OPEN, 2, WINDOWING_MODE_FULLSCREEN),
+ )
+ )
+
+ callOnTransitionReady(nextOpeningTransition)
+ callOnTransitionFinished()
+ // Assert that the visible list from top to bottom is valid
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(2)
+ assertVisibleTasks(listener, listOf(2, 1))
+
+ // Model the first task moving to front
+ val toFrontTransition =
+ createTransitionInfo(TRANSIT_TO_FRONT,
+ listOf(
+ createChange(TRANSIT_CHANGE, 1, WINDOWING_MODE_FULLSCREEN,
+ FLAG_MOVED_TO_TOP),
+ )
+ )
+
+ callOnTransitionReady(toFrontTransition)
+ callOnTransitionFinished()
+ // Assert that the visible list from top to bottom is valid
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(3)
+ assertVisibleTasks(listener, listOf(1, 2))
+ }
- override fun onTaskMovedToFrontThroughTransition(
- taskInfo: ActivityManager.RunningTaskInfo
- ) {
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ fun closeTransition_visibleTasksChanged() {
+ val listener = TestListener()
+ val executor = TestSyncExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+ // Model an opening task
+ val firstOpeningTransition =
+ createTransitionInfo(TRANSIT_OPEN,
+ listOf(
+ createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FULLSCREEN),
+ )
+ )
+
+ callOnTransitionReady(firstOpeningTransition)
+ callOnTransitionFinished()
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(1)
+
+ // Model a closing task
+ val nextOpeningTransition =
+ createTransitionInfo(TRANSIT_OPEN,
+ listOf(
+ createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FULLSCREEN),
+ )
+ )
+
+ callOnTransitionReady(nextOpeningTransition)
+ // Assert that the visible list hasn't changed (the close is pending)
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(1)
+
+ callOnTransitionFinished()
+ // Assert that after the transition finishes, there is only the opening task remaining
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(2)
+ assertVisibleTasks(listener, listOf())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ fun changeTransition_visibleTasksUnchanged() {
+ val listener = TestListener()
+ val executor = TestSyncExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+ // Model an opening task
+ val firstOpeningTransition =
+ createTransitionInfo(TRANSIT_OPEN,
+ listOf(
+ createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FULLSCREEN),
+ )
+ )
+
+ callOnTransitionReady(firstOpeningTransition)
+ callOnTransitionFinished()
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(1)
+
+ // Model a closing task
+ val nextOpeningTransition =
+ createTransitionInfo(
+ TRANSIT_FIRST_CUSTOM,
+ listOf(
+ createChange(TRANSIT_CHANGE, 1, WINDOWING_MODE_FULLSCREEN),
+ )
+ )
+
+ callOnTransitionReady(nextOpeningTransition)
+ // Assert that the visible list hasn't changed
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(1)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ fun taskVanished_visibleTasksChanged() {
+ val listener = TestListener()
+ val executor = TestSyncExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+ // Model an opening task
+ val firstOpeningTransition =
+ createTransitionInfo(TRANSIT_OPEN,
+ listOf(
+ createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FULLSCREEN),
+ )
+ )
+
+ callOnTransitionReady(firstOpeningTransition)
+ callOnTransitionFinished()
+ // Assert that the task is reported visible
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(1)
+ assertVisibleTasks(listener, listOf(1))
+
+ // Trigger task vanished
+ val removedTaskInfo = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)
+ transitionObserver.onTaskVanished(removedTaskInfo)
+
+ // Assert that the visible list is now empty
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(2)
+ assertVisibleTasks(listener, listOf())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ fun alwaysOnTop_taskIsTopMostVisible() {
+ val listener = TestListener()
+ val executor = TestSyncExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+ // Model an opening PIP task
+ val pipOpeningTransition =
+ createTransitionInfo(TRANSIT_OPEN,
+ listOf(
+ createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_PINNED),
+ )
+ )
+
+ callOnTransitionReady(pipOpeningTransition)
+ callOnTransitionFinished()
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(1)
+ assertVisibleTasks(listener, listOf(1))
+
+ // Model an opening fullscreen task
+ val firstOpeningTransition =
+ createTransitionInfo(TRANSIT_OPEN,
+ listOf(
+ createChange(TRANSIT_OPEN, 2, WINDOWING_MODE_FULLSCREEN),
+ )
+ )
+
+ callOnTransitionReady(firstOpeningTransition)
+ callOnTransitionFinished()
+ assertThat(listener.visibleTasksUpdatedCount).isEqualTo(2)
+ assertVisibleTasks(listener, listOf(1, 2))
+ }
+
+ class TestListener : TaskStackTransitionObserver.TaskStackTransitionObserverListener {
+ // Only used if FLAG_ENABLE_SHELL_TOP_TASK_TRACKING is disabled
+ var taskInfoOnTaskMovedToFront = RunningTaskInfo()
+ var taskInfoOnTaskChanged = mutableListOf<RunningTaskInfo>()
+ // Only used if FLAG_ENABLE_SHELL_TOP_TASK_TRACKING is enabled
+ var visibleTasks = mutableListOf<TaskInfo>()
+ var visibleTasksUpdatedCount = 0
+
+ override fun onTaskMovedToFrontThroughTransition(taskInfo: RunningTaskInfo) {
taskInfoOnTaskMovedToFront = taskInfo
}
- override fun onTaskChangedThroughTransition(taskInfo: ActivityManager.RunningTaskInfo) {
+ override fun onTaskChangedThroughTransition(taskInfo: RunningTaskInfo) {
taskInfoOnTaskChanged += taskInfo
}
+
+ override fun onVisibleTasksChanged(visibleTasks: List<RunningTaskInfo>) {
+ this.visibleTasks.clear()
+ this.visibleTasks.addAll(visibleTasks)
+ visibleTasksUpdatedCount++
+ }
}
/** Simulate calling the onTransitionReady() method */
@@ -412,27 +676,64 @@ class TaskStackTransitionObserverTest {
transitionObserver.onTransitionMerged(merged, playing)
}
+ /**
+ * Asserts that the listener has the given expected task ids (in order).
+ */
+ private fun assertVisibleTasks(
+ listener: TestListener,
+ expectedVisibleTaskIds: List<Int>
+ ) {
+ assertThat(listener.visibleTasks.size).isEqualTo(expectedVisibleTaskIds.size)
+ expectedVisibleTaskIds.forEachIndexed { index, taskId ->
+ assertThat(listener.visibleTasks[index].taskId).isEqualTo(taskId)
+ }
+ }
+
companion object {
- fun createTaskInfo(taskId: Int, windowingMode: Int): ActivityManager.RunningTaskInfo {
- val taskInfo = ActivityManager.RunningTaskInfo()
+ fun createTaskInfo(taskId: Int, windowingMode: Int): RunningTaskInfo {
+ val taskInfo = RunningTaskInfo()
+ taskInfo.baseIntent = Intent().setComponent(
+ ComponentName(javaClass.packageName, "Test"))
taskInfo.taskId = taskId
taskInfo.configuration.windowConfiguration.windowingMode = windowingMode
-
+ if (windowingMode == WINDOWING_MODE_PINNED) {
+ taskInfo.configuration.windowConfiguration.isAlwaysOnTop = true
+ }
return taskInfo
}
fun createChange(
mode: Int,
- taskInfo: ActivityManager.RunningTaskInfo
+ taskInfo: RunningTaskInfo,
+ flags: Int = 0,
): TransitionInfo.Change {
val change =
TransitionInfo.Change(
WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java)),
Mockito.mock(SurfaceControl::class.java)
)
+ change.flags = flags
change.mode = mode
change.taskInfo = taskInfo
return change
}
+
+ fun createChange(
+ mode: Int,
+ taskId: Int,
+ windowingMode: Int,
+ flags: Int = 0,
+ ): TransitionInfo.Change {
+ return createChange(mode, createTaskInfo(taskId, windowingMode), flags)
+ }
+
+ fun createTransitionInfo(
+ transitionType: Int,
+ changes: List<TransitionInfo.Change>
+ ): TransitionInfo {
+ return TransitionInfoBuilder(transitionType, 0)
+ .apply { changes.forEach { c -> this@apply.addChange(c) } }
+ .build()
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index c36b88e34835..71af97e5add3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -43,6 +43,7 @@ import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import com.android.wm.shell.TestSyncExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.sysui.ShellInit;
@@ -475,27 +476,6 @@ public class UnfoldTransitionHandlerTest {
}
}
- private static class TestSyncExecutor implements ShellExecutor {
- @Override
- public void execute(Runnable runnable) {
- runnable.run();
- }
-
- @Override
- public void executeDelayed(Runnable runnable, long delayMillis) {
- runnable.run();
- }
-
- @Override
- public void removeCallbacks(Runnable runnable) {
- }
-
- @Override
- public boolean hasCallback(Runnable runnable) {
- return false;
- }
- }
-
private TransitionInfo createUnfoldTransitionInfo() {
TransitionInfo transitionInfo = new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0);
TransitionInfo.Change change = new TransitionInfo.Change(null, mock(SurfaceControl.class));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 1d2d0f078817..f653622d0460 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -30,6 +30,7 @@ import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
import static com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.CLOSE_MAXIMIZE_MENU_DELAY_MS;
+import static com.android.wm.shell.windowdecor.WindowDecoration.INVALID_CORNER_RADIUS;
import static com.google.common.truth.Truth.assertThat;
@@ -219,7 +220,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Captor
private ArgumentCaptor<Runnable> mCloseMaxMenuRunnable;
- private final InsetsState mInsetsState = new InsetsState();
+ private final InsetsState mInsetsState = createInsetsState(statusBars(), /* visible= */true);
private SurfaceControl.Transaction mMockTransaction;
private StaticMockitoSession mMockitoSession;
private TestableContext mTestableContext;
@@ -312,8 +313,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Test
- public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersAreEnabled() {
+ public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersSetForFreeform() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
fillRoundedCornersResources(/* fillValue= */ 30);
RelayoutParams relayoutParams = new RelayoutParams();
@@ -334,6 +336,29 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Test
+ public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForFullscreen() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ fillRoundedCornersResources(/* fillValue= */ 30);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ false,
+ new InsetsState(),
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
+
+ assertThat(relayoutParams.mCornerRadius).isEqualTo(INVALID_CORNER_RADIUS);
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY)
public void updateRelayoutParams_appHeader_usesTaskDensity() {
final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources()
@@ -1408,8 +1433,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
public void notifyCaptionStateChanged_flagDisabled_doNoNotify() {
when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- when(mMockDisplayController.getInsetsState(taskInfo.displayId))
- .thenReturn(createInsetsState(statusBars(), /* visible= */true));
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -1423,8 +1446,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
public void notifyCaptionStateChanged_inFullscreenMode_notifiesAppHandleVisible() {
when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- when(mMockDisplayController.getInsetsState(taskInfo.displayId))
- .thenReturn(createInsetsState(statusBars(), /* visible= */true));
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
@@ -1444,8 +1465,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
public void notifyCaptionStateChanged_inWindowingMode_notifiesAppHeaderVisible() {
when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- when(mMockDisplayController.getInsetsState(taskInfo.displayId))
- .thenReturn(createInsetsState(statusBars(), /* visible= */true));
when(mMockAppHeaderViewHolder.getAppChipLocationInWindow()).thenReturn(
new Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3));
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -1473,8 +1492,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
public void notifyCaptionStateChanged_taskNotVisible_notifiesNoCaptionVisible() {
when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ false);
- when(mMockDisplayController.getInsetsState(taskInfo.displayId))
- .thenReturn(createInsetsState(statusBars(), /* visible= */true));
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
@@ -1493,8 +1510,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
public void notifyCaptionStateChanged_captionHandleExpanded_notifiesHandleMenuExpanded() {
when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- when(mMockDisplayController.getInsetsState(taskInfo.displayId))
- .thenReturn(createInsetsState(statusBars(), /* visible= */true));
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
@@ -1518,8 +1533,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
public void notifyCaptionStateChanged_captionHandleClosed_notifiesHandleMenuClosed() {
when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- when(mMockDisplayController.getInsetsState(taskInfo.displayId))
- .thenReturn(createInsetsState(statusBars(), /* visible= */true));
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index fcb7efc35c94..e2db2c9e3827 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -355,6 +355,7 @@ cc_defaults {
"jni/AnimatedImageDrawable.cpp",
"jni/Bitmap.cpp",
"jni/BitmapRegionDecoder.cpp",
+ "jni/RuntimeXfermode.cpp",
"jni/BufferUtils.cpp",
"jni/HardwareBufferHelpers.cpp",
"jni/BitmapFactory.cpp",
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 15b2bac50c79..56de56805be4 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -28,6 +28,7 @@
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
+extern int register_android_graphics_RuntimeXfermode(JNIEnv*);
extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_Camera(JNIEnv* env);
extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
@@ -107,6 +108,7 @@ extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
REG_JNI(register_android_graphics_Bitmap),
REG_JNI(register_android_graphics_BitmapFactory),
REG_JNI(register_android_graphics_BitmapRegionDecoder),
+ REG_JNI(register_android_graphics_RuntimeXfermode),
REG_JNI(register_android_graphics_ByteBufferStreamAdaptor),
REG_JNI(register_android_graphics_Camera),
REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index da237928e5e1..a7d855d7e8ca 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -906,6 +906,13 @@ namespace PaintGlue {
paint->setBlendMode(mode);
}
+ static void setRuntimeXfermode(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle,
+ jlong xfermodeHandle) {
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ SkBlender* blender = reinterpret_cast<SkBlender*>(xfermodeHandle);
+ paint->setBlender(sk_ref_sp(blender));
+ }
+
static jlong setPathEffect(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong effectHandle) {
Paint* obj = reinterpret_cast<Paint*>(objHandle);
SkPathEffect* effect = reinterpret_cast<SkPathEffect*>(effectHandle);
@@ -1233,6 +1240,7 @@ static const JNINativeMethod methods[] = {
{"nSetShader", "(JJ)J", (void*)PaintGlue::setShader},
{"nSetColorFilter", "(JJ)J", (void*)PaintGlue::setColorFilter},
{"nSetXfermode", "(JI)V", (void*)PaintGlue::setXfermode},
+ {"nSetXfermode", "(JJ)V", (void*)PaintGlue::setRuntimeXfermode},
{"nSetPathEffect", "(JJ)J", (void*)PaintGlue::setPathEffect},
{"nSetMaskFilter", "(JJ)J", (void*)PaintGlue::setMaskFilter},
{"nSetTypeface", "(JJ)V", (void*)PaintGlue::setTypeface},
diff --git a/libs/hwui/jni/RuntimeEffectUtils.cpp b/libs/hwui/jni/RuntimeEffectUtils.cpp
index 46db8633c66e..ad0e540b5b40 100644
--- a/libs/hwui/jni/RuntimeEffectUtils.cpp
+++ b/libs/hwui/jni/RuntimeEffectUtils.cpp
@@ -90,7 +90,7 @@ void UpdateChild(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* child
SkFlattenable* childEffect) {
SkRuntimeShaderBuilder::BuilderChild builderChild = builder->child(childName);
if (builderChild.fChild == nullptr) {
- ThrowIAEFmt(env, "unable to find shader named %s", childName);
+ ThrowIAEFmt(env, "unable to find child named %s", childName);
return;
}
diff --git a/libs/hwui/jni/RuntimeXfermode.cpp b/libs/hwui/jni/RuntimeXfermode.cpp
new file mode 100644
index 000000000000..c1c8964bf5eb
--- /dev/null
+++ b/libs/hwui/jni/RuntimeXfermode.cpp
@@ -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.
+ */
+
+#include "GraphicsJNI.h"
+#include "RuntimeEffectUtils.h"
+#include "SkBlender.h"
+
+using namespace android::uirenderer;
+
+static void SkRuntimeEffectBuilder_delete(SkRuntimeEffectBuilder* builder) {
+ delete builder;
+}
+
+static jlong RuntimeXfermode_getNativeFinalizer(JNIEnv*, jobject) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&SkRuntimeEffectBuilder_delete));
+}
+
+static jlong RuntimeXfermode_createBuilder(JNIEnv* env, jobject, jstring sksl) {
+ ScopedUtfChars strSksl(env, sksl);
+ auto result =
+ SkRuntimeEffect::MakeForBlender(SkString(strSksl.c_str()), SkRuntimeEffect::Options{});
+ if (result.effect.get() == nullptr) {
+ doThrowIAE(env, result.errorText.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(new SkRuntimeEffectBuilder(std::move(result.effect)));
+}
+
+static jlong RuntimeXfermode_create(JNIEnv* env, jobject, jlong builderPtr) {
+ auto* builder = reinterpret_cast<SkRuntimeEffectBuilder*>(builderPtr);
+ sk_sp<SkBlender> blender = builder->makeBlender();
+ if (!blender) {
+ doThrowIAE(env);
+ }
+ return reinterpret_cast<jlong>(blender.release());
+}
+
+static void RuntimeXfermode_updateFloatArrayUniforms(JNIEnv* env, jobject, jlong builderPtr,
+ jstring uniformName, jfloatArray uniforms,
+ jboolean isColor) {
+ auto* builder = reinterpret_cast<SkRuntimeEffectBuilder*>(builderPtr);
+ ScopedUtfChars name(env, uniformName);
+ AutoJavaFloatArray autoValues(env, uniforms, 0, kRO_JNIAccess);
+ UpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(), isColor);
+}
+
+static void RuntimeXfermode_updateFloatUniforms(JNIEnv* env, jobject, jlong builderPtr,
+ jstring uniformName, jfloat value1, jfloat value2,
+ jfloat value3, jfloat value4, jint count) {
+ auto* builder = reinterpret_cast<SkRuntimeEffectBuilder*>(builderPtr);
+ ScopedUtfChars name(env, uniformName);
+ const float values[4] = {value1, value2, value3, value4};
+ UpdateFloatUniforms(env, builder, name.c_str(), values, count, false);
+}
+
+static void RuntimeXfermode_updateIntArrayUniforms(JNIEnv* env, jobject, jlong builderPtr,
+ jstring uniformName, jintArray uniforms) {
+ auto* builder = reinterpret_cast<SkRuntimeEffectBuilder*>(builderPtr);
+ ScopedUtfChars name(env, uniformName);
+ AutoJavaIntArray autoValues(env, uniforms, 0);
+ UpdateIntUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length());
+}
+
+static void RuntimeXfermode_updateIntUniforms(JNIEnv* env, jobject, jlong builderPtr,
+ jstring uniformName, jint value1, jint value2,
+ jint value3, jint value4, jint count) {
+ auto* builder = reinterpret_cast<SkRuntimeEffectBuilder*>(builderPtr);
+ ScopedUtfChars name(env, uniformName);
+ const int values[4] = {value1, value2, value3, value4};
+ UpdateIntUniforms(env, builder, name.c_str(), values, count);
+}
+
+static void RuntimeXfermode_updateChild(JNIEnv* env, jobject, jlong builderPtr, jstring childName,
+ jlong childPtr) {
+ auto* builder = reinterpret_cast<SkRuntimeEffectBuilder*>(builderPtr);
+ ScopedUtfChars name(env, childName);
+ auto* child = reinterpret_cast<SkFlattenable*>(childPtr);
+ if (child) {
+ UpdateChild(env, builder, name.c_str(), child);
+ }
+}
+
+static const JNINativeMethod gRuntimeXfermodeMethods[] = {
+ {"nativeGetFinalizer", "()J", (void*)RuntimeXfermode_getNativeFinalizer},
+ {"nativeCreateBlenderBuilder", "(Ljava/lang/String;)J",
+ (void*)RuntimeXfermode_createBuilder},
+ {"nativeCreateNativeInstance", "(J)J", (void*)RuntimeXfermode_create},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V",
+ (void*)RuntimeXfermode_updateFloatArrayUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V",
+ (void*)RuntimeXfermode_updateFloatUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V",
+ (void*)RuntimeXfermode_updateIntArrayUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
+ (void*)RuntimeXfermode_updateIntUniforms},
+ {"nativeUpdateChild", "(JLjava/lang/String;J)V", (void*)RuntimeXfermode_updateChild},
+};
+
+int register_android_graphics_RuntimeXfermode(JNIEnv* env) {
+ android::RegisterMethodsOrDie(env, "android/graphics/RuntimeXfermode", gRuntimeXfermodeMethods,
+ NELEM(gRuntimeXfermodeMethods));
+
+ return 0;
+}
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 2a057e7a4cdc..018c2b1374d0 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -2,6 +2,7 @@
#include "Gainmap.h"
#include "GraphicsJNI.h"
+#include "RuntimeEffectUtils.h"
#include "SkBitmap.h"
#include "SkBlendMode.h"
#include "SkColor.h"
@@ -280,50 +281,6 @@ static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
return ret;
}
-static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
- switch (type) {
- case SkRuntimeEffect::Uniform::Type::kFloat:
- case SkRuntimeEffect::Uniform::Type::kFloat2:
- case SkRuntimeEffect::Uniform::Type::kFloat3:
- case SkRuntimeEffect::Uniform::Type::kFloat4:
- case SkRuntimeEffect::Uniform::Type::kFloat2x2:
- case SkRuntimeEffect::Uniform::Type::kFloat3x3:
- case SkRuntimeEffect::Uniform::Type::kFloat4x4:
- return false;
- case SkRuntimeEffect::Uniform::Type::kInt:
- case SkRuntimeEffect::Uniform::Type::kInt2:
- case SkRuntimeEffect::Uniform::Type::kInt3:
- case SkRuntimeEffect::Uniform::Type::kInt4:
- return true;
- }
-}
-
-static void UpdateFloatUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder,
- const char* uniformName, const float values[], int count,
- bool isColor) {
- SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName);
- if (uniform.fVar == nullptr) {
- ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
- } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
- if (isColor) {
- jniThrowExceptionFmt(
- env, "java/lang/IllegalArgumentException",
- "attempting to set a color uniform using the non-color specific APIs: %s %x",
- uniformName, uniform.fVar->flags);
- } else {
- ThrowIAEFmt(env,
- "attempting to set a non-color uniform using the setColorUniform APIs: %s",
- uniformName);
- }
- } else if (isIntUniformType(uniform.fVar->type)) {
- ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
- uniformName);
- } else if (!uniform.set<float>(values, count)) {
- ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
- uniform.fVar->sizeInBytes(), sizeof(float) * count);
- }
-}
-
static void RuntimeShader_updateFloatUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
jstring jUniformName, jfloat value1, jfloat value2,
jfloat value3, jfloat value4, jint count) {
@@ -342,20 +299,6 @@ static void RuntimeShader_updateFloatArrayUniforms(JNIEnv* env, jobject, jlong s
UpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(), isColor);
}
-static void UpdateIntUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder, const char* uniformName,
- const int values[], int count) {
- SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName);
- if (uniform.fVar == nullptr) {
- ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
- } else if (!isIntUniformType(uniform.fVar->type)) {
- ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
- uniformName);
- } else if (!uniform.set<int>(values, count)) {
- ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
- uniform.fVar->sizeInBytes(), sizeof(float) * count);
- }
-}
-
static void RuntimeShader_updateIntUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
jstring jUniformName, jint value1, jint value2,
jint value3, jint value4, jint count) {
@@ -388,6 +331,15 @@ static void RuntimeShader_updateShader(JNIEnv* env, jobject, jlong shaderBuilder
builder->child(name.c_str()) = sk_ref_sp(shader);
}
+static void RuntimeShader_updateChild(JNIEnv* env, jobject, jlong shaderBuilder,
+ jstring jUniformName, jlong childHandle) {
+ SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
+ ScopedUtfChars name(env, jUniformName);
+ auto* childEffect = reinterpret_cast<SkFlattenable*>(childHandle);
+
+ UpdateChild(env, builder, name.c_str(), childEffect);
+}
+
///////////////////////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gShaderMethods[] = {
@@ -428,6 +380,7 @@ static const JNINativeMethod gRuntimeShaderMethods[] = {
{"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
(void*)RuntimeShader_updateIntUniforms},
{"nativeUpdateShader", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateShader},
+ {"nativeUpdateChild", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateChild},
};
int register_android_graphics_Shader(JNIEnv* env)
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 3cd5f5266ef2..da50f2cd86c4 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -19,6 +19,7 @@ package android.media;
import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_ALL;
import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE;
import static android.media.audio.Flags.FLAG_MUTED_BY_PORT_VOLUME_API;
+import static android.media.audio.Flags.FLAG_ROUTED_DEVICE_IDS;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -39,6 +40,8 @@ import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
@@ -461,8 +464,12 @@ public final class AudioPlaybackConfiguration implements Parcelable {
/**
* Returns information about the {@link AudioDeviceInfo} used for this playback.
- * @return the audio playback device or null if the device is not available at the time of query
+ * @return the audio playback device or null if the device is not available at the time of
+ * query.
+ * @deprecated this information was never populated
*/
+ @Deprecated
+ @FlaggedApi(FLAG_ROUTED_DEVICE_IDS)
public @Nullable AudioDeviceInfo getAudioDeviceInfo() {
final int deviceId;
synchronized (mUpdateablePropLock) {
@@ -476,6 +483,23 @@ public final class AudioPlaybackConfiguration implements Parcelable {
/**
* @hide
+ * Returns information about the List of {@link AudioDeviceInfo} used for this playback.
+ * @return the audio playback devices
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_ROUTED_DEVICE_IDS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public @NonNull List<AudioDeviceInfo> getAudioDeviceInfos() {
+ List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>();
+ AudioDeviceInfo audioDeviceInfo = getAudioDeviceInfo();
+ if (audioDeviceInfo != null) {
+ audioDeviceInfos.add(audioDeviceInfo);
+ }
+ return audioDeviceInfos;
+ }
+
+ /**
+ * @hide
* Return the audio session ID associated with this player.
* See {@link AudioManager#generateAudioSessionId()}.
* @return an audio session ID
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 80e57193d0dc..939494152116 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -20,8 +20,10 @@ import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAUL
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+import static android.media.audio.Flags.FLAG_ROUTED_DEVICE_IDS;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -1920,6 +1922,23 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
}
/**
+ * Returns a List of {@link AudioDeviceInfo} identifying the current routing of this
+ * AudioRecord.
+ * Note: The query is only valid if the AudioRecord is currently playing. If it is not,
+ * <code>getRoutedDevices()</code> will return an empty list.
+ */
+ @Override
+ @FlaggedApi(FLAG_ROUTED_DEVICE_IDS)
+ public @NonNull List<AudioDeviceInfo> getRoutedDevices() {
+ List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>();
+ AudioDeviceInfo audioDeviceInfo = getRoutedDevice();
+ if (audioDeviceInfo != null) {
+ audioDeviceInfos.add(audioDeviceInfo);
+ }
+ return audioDeviceInfos;
+ }
+
+ /**
* Must match the native definition in frameworks/av/service/audioflinger/Audioflinger.h.
*/
private static final long MAX_SHARED_AUDIO_HISTORY_MS = 5000;
diff --git a/media/java/android/media/AudioRouting.java b/media/java/android/media/AudioRouting.java
index 26fa631ac6ac..22aa9a09d560 100644
--- a/media/java/android/media/AudioRouting.java
+++ b/media/java/android/media/AudioRouting.java
@@ -16,9 +16,16 @@
package android.media;
+import static android.media.audio.Flags.FLAG_ROUTED_DEVICE_IDS;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
import android.os.Handler;
import android.os.Looper;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* AudioRouting defines an interface for controlling routing and routing notifications in
* AudioTrack and AudioRecord objects.
@@ -49,6 +56,22 @@ public interface AudioRouting {
public AudioDeviceInfo getRoutedDevice();
/**
+ * Returns a List of {@link AudioDeviceInfo} identifying the current routing of this
+ * AudioTrack/AudioRecord.
+ * Note: The query is only valid if the AudioTrack/AudioRecord is currently playing.
+ * If it is not, <code>getRoutedDevices()</code> will return an empty List.
+ */
+ @FlaggedApi(FLAG_ROUTED_DEVICE_IDS)
+ default @NonNull List<AudioDeviceInfo> getRoutedDevices() {
+ List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>();
+ AudioDeviceInfo audioDeviceInfo = getRoutedDevice();
+ if (audioDeviceInfo != null) {
+ audioDeviceInfos.add(audioDeviceInfo);
+ }
+ return new ArrayList<AudioDeviceInfo>();
+ }
+
+ /**
* Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
* changes on this AudioTrack/AudioRecord.
* @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 03cd53580b1b..93a183188793 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -17,8 +17,10 @@
package android.media;
import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+import static android.media.audio.Flags.FLAG_ROUTED_DEVICE_IDS;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -54,7 +56,9 @@ import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.NioUtils;
+import java.util.ArrayList;
import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -3783,6 +3787,8 @@ public class AudioTrack extends PlayerBase
* Returns an {@link AudioDeviceInfo} identifying the current routing of this AudioTrack.
* Note: The query is only valid if the AudioTrack is currently playing. If it is not,
* <code>getRoutedDevice()</code> will return null.
+ * Audio may play on multiple devices simultaneously (e.g. an alarm playing on headphones and
+ * speaker on a phone), so prefer using {@link #getRoutedDevices}.
*/
@Override
public AudioDeviceInfo getRoutedDevice() {
@@ -3793,6 +3799,23 @@ public class AudioTrack extends PlayerBase
return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_OUTPUTS);
}
+ /**
+ * Returns a List of {@link AudioDeviceInfo} identifying the current routing of this
+ * AudioTrack.
+ * Note: The query is only valid if the AudioTrack is currently playing. If it is not,
+ * <code>getRoutedDevices()</code> will return an empty list.
+ */
+ @Override
+ @FlaggedApi(FLAG_ROUTED_DEVICE_IDS)
+ public @NonNull List<AudioDeviceInfo> getRoutedDevices() {
+ List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>();
+ AudioDeviceInfo audioDeviceInfo = getRoutedDevice();
+ if (audioDeviceInfo != null) {
+ audioDeviceInfos.add(audioDeviceInfo);
+ }
+ return audioDeviceInfos;
+ }
+
private void tryToDisableNativeRoutingCallback() {
synchronized (mRoutingChangeListeners) {
if (mEnableSelfRoutingMonitor) {
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 93259992d339..0a79f41e1ff0 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -1157,7 +1157,11 @@ public class ImageWriter implements AutoCloseable {
@Override
public void setFence(@NonNull SyncFence fence) throws IOException {
throwISEIfImageIsInvalid();
- nativeSetFenceFd(fence.getFdDup().detachFd());
+ if (fence.isValid()) {
+ nativeSetFenceFd(fence.getFdDup().detachFd());
+ } else {
+ nativeSetFenceFd(-1);
+ }
}
@Override
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 3f9126aa9456..1ecba31ce07f 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -16,10 +16,9 @@
package android.media;
+import static android.media.tv.flags.Flags.FLAG_MEDIACAS_UPDATE_CLIENT_PROFILE_PRIORITY;
import static android.media.tv.flags.Flags.FLAG_SET_RESOURCE_HOLDER_RETAIN;
-import static com.android.media.flags.Flags.FLAG_UPDATE_CLIENT_PROFILE_PRIORITY;
-
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -996,7 +995,7 @@ public final class MediaCas implements AutoCloseable {
* @param niceValue the nice value.
* @hide
*/
- @FlaggedApi(FLAG_UPDATE_CLIENT_PROFILE_PRIORITY)
+ @FlaggedApi(FLAG_MEDIACAS_UPDATE_CLIENT_PROFILE_PRIORITY)
@SystemApi
@RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
public boolean updateResourcePriority(int priority, int niceValue) {
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index a0f8ae5defeb..158bc7fcd482 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -18,7 +18,9 @@ package android.media;
import static android.Manifest.permission.BIND_IMS_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.media.audio.Flags.FLAG_ROUTED_DEVICE_IDS;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -84,6 +86,7 @@ import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
import java.nio.ByteOrder;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
@@ -1542,6 +1545,8 @@ public class MediaPlayer extends PlayerBase
* Note: The query is only valid if the MediaPlayer is currently playing.
* If the player is not playing, the returned device can be null or correspond to previously
* selected device when the player was last active.
+ * Audio may play on multiple devices simultaneously (e.g. an alarm playing on headphones and
+ * speaker on a phone), so prefer using {@link #getRoutedDevices}.
*/
@Override
public AudioDeviceInfo getRoutedDevice() {
@@ -1552,6 +1557,23 @@ public class MediaPlayer extends PlayerBase
return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_OUTPUTS);
}
+ /**
+ * Returns a List of {@link AudioDeviceInfo} identifying the current routing of this
+ * MediaPlayer.
+ * Note: The query is only valid if the MediaPlayer is currently playing.
+ * If the player is not playing, the returned devices can be empty or correspond to previously
+ * selected devices when the player was last active.
+ */
+ @Override
+ @FlaggedApi(FLAG_ROUTED_DEVICE_IDS)
+ public @NonNull List<AudioDeviceInfo> getRoutedDevices() {
+ List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>();
+ AudioDeviceInfo audioDeviceInfo = getRoutedDevice();
+ if (audioDeviceInfo != null) {
+ audioDeviceInfos.add(audioDeviceInfo);
+ }
+ return audioDeviceInfos;
+ }
/**
* Sends device list change notification to all listeners.
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 2d17bf500f12..f75bcf3c437d 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -16,7 +16,10 @@
package android.media;
+import static android.media.audio.Flags.FLAG_ROUTED_DEVICE_IDS;
+
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -1695,6 +1698,24 @@ public class MediaRecorder implements AudioRouting,
return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_INPUTS);
}
+ /**
+ * Returns a List of {@link AudioDeviceInfo} identifying the current routing of this
+ * MediaRecorder.
+ * Note: The query is only valid if the MediaRecorder is currently recording.
+ * If the recorder is not recording, the returned devices can be empty or correspond to
+ * previously selected devices when the recorder was last active.
+ */
+ @Override
+ @FlaggedApi(FLAG_ROUTED_DEVICE_IDS)
+ public @NonNull List<AudioDeviceInfo> getRoutedDevices() {
+ List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>();
+ AudioDeviceInfo audioDeviceInfo = getRoutedDevice();
+ if (audioDeviceInfo != null) {
+ audioDeviceInfos.add(audioDeviceInfo);
+ }
+ return audioDeviceInfos;
+ }
+
/*
* Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
*/
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 7895eb27b372..d8a8c8b0ee2a 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -1,13 +1,7 @@
package: "com.android.media.flags"
container: "system"
-flag {
- name: "enable_rlp_callbacks_in_media_router2"
- is_exported: true
- namespace: "media_solutions"
- description: "Make RouteListingPreference getter and callbacks public in MediaRouter2."
- bug: "281067101"
-}
+# Flags are ordered alphabetically by name.
flag {
name: "adjust_volume_for_foreground_app_playing_audio_without_media_session"
@@ -17,6 +11,13 @@ flag {
}
flag {
+ name: "enable_audio_input_device_routing_and_volume_control"
+ namespace: "media_better_together"
+ description: "Allows audio input devices routing and volume control via system settings."
+ bug: "355684672"
+}
+
+flag {
name: "enable_audio_policies_device_and_bluetooth_controller"
is_exported: true
namespace: "media_solutions"
@@ -25,71 +26,70 @@ flag {
}
flag {
- name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling"
- namespace: "media_solutions"
- description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
- bug: "293743975"
-}
-
-flag {
- name: "enable_waiting_state_for_system_session_creation_request"
- namespace: "media_solutions"
- description: "Introduces a waiting state for the session creation request and prevents it from early failing when the selectedRoute from the bluetooth stack doesn't match the pending request route id."
- bug: "307723189"
+ name: "enable_built_in_speaker_route_suitability_statuses"
+ is_exported: true
+ namespace: "media_solutions"
+ description: "Make MediaRoute2Info provide information about routes suitability for transfer."
+ bug: "279555229"
}
flag {
- name: "enable_new_media_route_2_info_types"
+ name: "enable_cross_user_routing_in_media_router2"
is_exported: true
namespace: "media_solutions"
- description: "Enables the following type constants in MediaRoute2Info: CAR, COMPUTER, GAME_CONSOLE, SMARTPHONE, SMARTWATCH, TABLET, TABLET_DOCKED. Note that this doesn't gate any behavior. It only guards some API int symbols."
- bug: "301713440"
+ description: "Allows clients of privileged MediaRouter2 that hold INTERACT_ACROSS_USERS_FULL to control routing across users."
+ bug: "288580225"
}
flag {
- name: "enable_new_wired_media_route_2_info_types"
- is_exported: true
- namespace: "media_tv"
- description: "Enables the following type constant in MediaRoute2Info: LINE_ANALOG, LINE_DIGITAL, AUX_LINE"
- bug: "375691732"
+ name: "enable_full_scan_with_media_content_control"
+ namespace: "media_better_together"
+ description: "Allows holders of the MEDIA_CONTENT_CONTROL permission to scan for routes while not in the foreground."
+ bug: "352401364"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
- name: "enable_privileged_routing_for_media_routing_control"
+ name: "enable_get_transferable_routes"
is_exported: true
namespace: "media_solutions"
- description: "Allow access to privileged routing capabilities to MEDIA_ROUTING_CONTROL holders."
- bug: "305919655"
+ description: "Exposes RoutingController#getTransferableRoutes() (previously hidden) to the public API."
+ bug: "323154573"
}
flag {
- name: "enable_cross_user_routing_in_media_router2"
- is_exported: true
- namespace: "media_solutions"
- description: "Allows clients of privileged MediaRouter2 that hold INTERACT_ACROSS_USERS_FULL to control routing across users."
- bug: "288580225"
+ name: "enable_mirroring_in_media_router_2"
+ namespace: "media_better_together"
+ description: "Enables support for mirroring routes in the MediaRouter2 framework, allowing Output Switcher to offer mirroring routes."
+ bug: "362507305"
}
flag {
- name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name"
+ name: "enable_mr2_service_non_main_bg_thread"
namespace: "media_solutions"
- description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos."
- bug: "314324170"
+ description: "Enables the use of a background thread in the media routing framework, instead of using the main thread."
+ bug: "310145678"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
- name: "update_client_profile_priority"
+ name: "enable_new_media_route_2_info_types"
+ is_exported: true
namespace: "media_solutions"
- description : "Feature flag to add updateResourcePriority api to MediaCas"
- bug: "300565729"
+ description: "Enables the following type constants in MediaRoute2Info: CAR, COMPUTER, GAME_CONSOLE, SMARTPHONE, SMARTWATCH, TABLET, TABLET_DOCKED. Note that this doesn't gate any behavior. It only guards some API int symbols."
+ bug: "301713440"
}
flag {
- name: "enable_built_in_speaker_route_suitability_statuses"
- is_exported: true
- namespace: "media_solutions"
- description: "Make MediaRoute2Info provide information about routes suitability for transfer."
- bug: "279555229"
+ name: "enable_new_wired_media_route_2_info_types"
+ is_exported: true
+ namespace: "media_tv"
+ description: "Enables the following type constant in MediaRoute2Info: LINE_ANALOG, LINE_DIGITAL, AUX_LINE"
+ bug: "375691732"
}
flag {
@@ -101,11 +101,10 @@ flag {
}
flag {
- name: "enable_get_transferable_routes"
- is_exported: true
+ name: "enable_null_session_in_media_browser_service"
namespace: "media_solutions"
- description: "Exposes RoutingController#getTransferableRoutes() (previously hidden) to the public API."
- bug: "323154573"
+ description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
+ bug: "185136506"
}
flag {
@@ -116,60 +115,56 @@ flag {
}
flag {
- name: "enable_mr2_service_non_main_bg_thread"
+ name: "enable_prevention_of_manager_scans_when_no_apps_scan"
namespace: "media_solutions"
- description: "Enables the use of a background thread in the media routing framework, instead of using the main thread."
- bug: "310145678"
+ description: "Prevents waking up route providers when no apps are scanning, even if SysUI or Settings are scanning."
+ bug: "319604673"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
- name: "enable_screen_off_scanning"
+ name: "enable_privileged_routing_for_media_routing_control"
is_exported: true
namespace: "media_solutions"
- description: "Enable new MediaRouter2 API to enable watch companion apps to scan while the phone screen is off."
- bug: "281072508"
+ description: "Allow access to privileged routing capabilities to MEDIA_ROUTING_CONTROL holders."
+ bug: "305919655"
}
flag {
- name: "enable_null_session_in_media_browser_service"
+ name: "enable_rlp_callbacks_in_media_router2"
+ is_exported: true
namespace: "media_solutions"
- description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
- bug: "185136506"
+ description: "Make RouteListingPreference getter and callbacks public in MediaRouter2."
+ bug: "281067101"
}
flag {
- name: "enable_prevention_of_manager_scans_when_no_apps_scan"
+ name: "enable_screen_off_scanning"
+ is_exported: true
namespace: "media_solutions"
- description: "Prevents waking up route providers when no apps are scanning, even if SysUI or Settings are scanning."
- bug: "319604673"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
+ description: "Enable new MediaRouter2 API to enable watch companion apps to scan while the phone screen is off."
+ bug: "281072508"
}
flag {
- name: "enable_full_scan_with_media_content_control"
- namespace: "media_better_together"
- description: "Allows holders of the MEDIA_CONTENT_CONTROL permission to scan for routes while not in the foreground."
- bug: "352401364"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
+ name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name"
+ namespace: "media_solutions"
+ description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos."
+ bug: "314324170"
}
flag {
- name: "enable_audio_input_device_routing_and_volume_control"
- namespace: "media_better_together"
- description: "Allows audio input devices routing and volume control via system settings."
- bug: "355684672"
+ name: "enable_waiting_state_for_system_session_creation_request"
+ namespace: "media_solutions"
+ description: "Introduces a waiting state for the session creation request and prevents it from early failing when the selectedRoute from the bluetooth stack doesn't match the pending request route id."
+ bug: "307723189"
}
flag {
- name: "enable_mirroring_in_media_router_2"
- namespace: "media_better_together"
- description: "Enables support for mirroring routes in the MediaRouter2 framework, allowing Output Switcher to offer mirroring routes."
- bug: "362507305"
+ name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling"
+ namespace: "media_solutions"
+ description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
+ bug: "293743975"
}
diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig
index 17d1ff6a86a7..1bb9a8e1d6d3 100644
--- a/media/java/android/media/flags/projection.aconfig
+++ b/media/java/android/media/flags/projection.aconfig
@@ -18,3 +18,10 @@ flag {
bug: "362720120"
is_exported: true
}
+
+flag {
+ namespace: "media_projection"
+ name: "stop_media_projection_on_call_end"
+ description: "Stops MediaProjection sessions when a call ends"
+ bug: "368336349"
+} \ No newline at end of file
diff --git a/media/java/android/media/quality/AmbientBacklightEvent.java b/media/java/android/media/quality/AmbientBacklightEvent.java
index 5c11def43209..273f21e01f86 100644
--- a/media/java/android/media/quality/AmbientBacklightEvent.java
+++ b/media/java/android/media/quality/AmbientBacklightEvent.java
@@ -40,7 +40,7 @@ public final class AmbientBacklightEvent implements Parcelable {
@IntDef({AMBIENT_BACKLIGHT_EVENT_ENABLED, AMBIENT_BACKLIGHT_EVENT_DISABLED,
AMBIENT_BACKLIGHT_EVENT_METADATA,
AMBIENT_BACKLIGHT_EVENT_INTERRUPTED})
- public @interface AmbientBacklightEventTypes {}
+ public @interface Type {}
/**
* Event type for ambient backlight events. The ambient backlight is enabled.
@@ -69,9 +69,9 @@ public final class AmbientBacklightEvent implements Parcelable {
private final AmbientBacklightMetadata mMetadata;
/**
- * Constructor of AmbientBacklightEvent.
+ * Constructs AmbientBacklightEvent.
*/
- public AmbientBacklightEvent(int eventType,
+ public AmbientBacklightEvent(@Type int eventType,
@Nullable AmbientBacklightMetadata metadata) {
mEventType = eventType;
mMetadata = metadata;
@@ -85,6 +85,7 @@ public final class AmbientBacklightEvent implements Parcelable {
/**
* Gets event type.
*/
+ @Type
public int getEventType() {
return mEventType;
}
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.java b/media/java/android/media/quality/AmbientBacklightMetadata.java
index 9c11f9a3e560..5cea10d2de43 100644
--- a/media/java/android/media/quality/AmbientBacklightMetadata.java
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.java
@@ -29,6 +29,9 @@ import java.util.Arrays;
/**
* Metadata of ambient backlight.
+ *
+ * <p>A metadata instance is sent from ambient backlight hardware in a {@link AmbientBacklightEvent}
+ * with {@link AmbientBacklightEvent#AMBIENT_BACKLIGHT_EVENT_METADATA}.
* @hide
*/
@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
@@ -44,10 +47,15 @@ public final class AmbientBacklightMetadata implements Parcelable {
private final int[] mZonesColors;
/**
- * Constructor of AmbientBacklightMetadata.
+ * Constructs AmbientBacklightMetadata.
*/
- public AmbientBacklightMetadata(@NonNull String packageName, int compressAlgorithm,
- int source, int colorFormat, int horizontalZonesNumber, int verticalZonesNumber,
+ public AmbientBacklightMetadata(
+ @NonNull String packageName,
+ @AmbientBacklightSettings.CompressAlgorithm int compressAlgorithm,
+ @AmbientBacklightSettings.Source int source,
+ @PixelFormat.Format int colorFormat,
+ int horizontalZonesNumber,
+ int verticalZonesNumber,
@NonNull int[] zonesColors) {
mPackageName = packageName;
mCompressAlgorithm = compressAlgorithm;
@@ -69,7 +77,7 @@ public final class AmbientBacklightMetadata implements Parcelable {
}
/**
- * Gets package name.
+ * Gets package name of the metadata.
* @hide
*/
@NonNull
@@ -102,7 +110,9 @@ public final class AmbientBacklightMetadata implements Parcelable {
}
/**
- * Gets the number of lights in each horizontal zone.
+ * Gets the number of horizontal color zones.
+ *
+ * <p>A color zone is a group of lights that always display the same color.
*/
@IntRange(from = 0)
public int getHorizontalZonesNumber() {
@@ -110,7 +120,9 @@ public final class AmbientBacklightMetadata implements Parcelable {
}
/**
- * Gets the number of lights in each vertical zone.
+ * Gets the number of vertical color zones.
+ *
+ * <p>A color zone is a group of lights that always display the same color.
*/
@IntRange(from = 0)
public int getVerticalZonesNumber() {
@@ -118,10 +130,11 @@ public final class AmbientBacklightMetadata implements Parcelable {
}
/**
+ * Gets color data of vertical color zones.
* @hide
*/
@NonNull
- public int[] getZonesColors() {
+ public int[] getVerticalZonesColors() {
return mZonesColors;
}
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java
index 4ed7bc79fdca..d904cf728da7 100644
--- a/media/java/android/media/quality/AmbientBacklightSettings.java
+++ b/media/java/android/media/quality/AmbientBacklightSettings.java
@@ -30,7 +30,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
- * Settings for ambient backlight.
+ * Settings to configure ambient backlight hardware.
* @hide
*/
@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
@@ -124,8 +124,13 @@ public final class AmbientBacklightSettings implements Parcelable {
/**
* Constructs AmbientBacklightSettings.
*/
- public AmbientBacklightSettings(int source, int maxFps, int colorFormat,
- int horizontalZonesNumber, int verticalZonesNumber, boolean isLetterboxOmitted,
+ public AmbientBacklightSettings(
+ @Source int source,
+ int maxFps,
+ @PixelFormat.Format int colorFormat,
+ int horizontalZonesNumber,
+ int verticalZonesNumber,
+ boolean isLetterboxOmitted,
int threshold) {
mSource = source;
mMaxFps = maxFps;
@@ -171,7 +176,9 @@ public final class AmbientBacklightSettings implements Parcelable {
}
/**
- * Gets the number of lights in each horizontal zone.
+ * Gets the number of horizontal color zones.
+ *
+ * <p>A color zone is a group of lights that always display the same color.
*/
@IntRange(from = 0)
public int getHorizontalZonesNumber() {
@@ -179,7 +186,9 @@ public final class AmbientBacklightSettings implements Parcelable {
}
/**
- * Gets the number of lights in each vertical zone.
+ * Gets the number of vertical color zones.
+ *
+ * <p>A color zone is a group of lights that always display the same color.
*/
@IntRange(from = 0)
public int getVerticalZonesNumber() {
@@ -187,7 +196,11 @@ public final class AmbientBacklightSettings implements Parcelable {
}
/**
- * Returns {@code true} if letter box is omitted; {@code false} otherwise.
+ * Returns {@code true} if the black portion of the screen in letter box mode is omitted;
+ * {@code false} otherwise.
+ *
+ * <p>Letter-box is a technique to keep the original aspect ratio when displayed on a screen
+ * with different aspect ratio. Black bars are added to the top and bottom.
* @hide
*/
public boolean isLetterboxOmitted() {
@@ -195,6 +208,10 @@ public final class AmbientBacklightSettings implements Parcelable {
}
/**
+ * Gets the detection threshold of the ambient light.
+ *
+ * <p>If the color of a color zone is changed by the difference is smaller than the threshold,
+ * the change is ignored.
* @hide
*/
public int getThreshold() {
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index 250d59b7c2d7..aaedf21999f7 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -42,10 +42,12 @@ interface IMediaQualityManager {
SoundProfile createSoundProfile(in SoundProfile pp);
void updateSoundProfile(in String id, in SoundProfile pp);
void removeSoundProfile(in String id);
- SoundProfile getSoundProfileById(in String id);
+ SoundProfile getSoundProfile(in int type, in String name);
List<SoundProfile> getSoundProfilesByPackage(in String packageName);
List<SoundProfile> getAvailableSoundProfiles();
List<String> getSoundProfilePackageNames();
+ List<String> getSoundProfileAllowList();
+ void setSoundProfileAllowList(in List<String> packages);
void registerPictureProfileCallback(in IPictureProfileCallback cb);
void registerSoundProfileCallback(in ISoundProfileCallback cb);
diff --git a/media/java/android/media/quality/ISoundProfileCallback.aidl b/media/java/android/media/quality/ISoundProfileCallback.aidl
index 72d1524198fd..9043757316bc 100644
--- a/media/java/android/media/quality/ISoundProfileCallback.aidl
+++ b/media/java/android/media/quality/ISoundProfileCallback.aidl
@@ -17,6 +17,7 @@
package android.media.quality;
+import android.media.quality.ParamCapability;
import android.media.quality.SoundProfile;
/**
@@ -24,7 +25,9 @@ import android.media.quality.SoundProfile;
* @hide
*/
oneway interface ISoundProfileCallback {
- void onSoundProfileAdded(in long id, in SoundProfile p);
- void onSoundProfileUpdated(in long id, in SoundProfile p);
- void onSoundProfileRemoved(in long id, in SoundProfile p);
+ void onSoundProfileAdded(in String id, in SoundProfile p);
+ void onSoundProfileUpdated(in String id, in SoundProfile p);
+ void onSoundProfileRemoved(in String id, in SoundProfile p);
+ void onParamCapabilitiesChanged(in String id, in List<ParamCapability> caps);
+ void onError(in int err);
}
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 4d4526cf9925..dcf497122053 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -111,7 +111,7 @@ public final class MediaQualityManager {
};
ISoundProfileCallback spCallback = new ISoundProfileCallback.Stub() {
@Override
- public void onSoundProfileAdded(long profileId, SoundProfile profile) {
+ public void onSoundProfileAdded(String profileId, SoundProfile profile) {
synchronized (mLock) {
for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
// TODO: filter callback record
@@ -120,7 +120,7 @@ public final class MediaQualityManager {
}
}
@Override
- public void onSoundProfileUpdated(long profileId, SoundProfile profile) {
+ public void onSoundProfileUpdated(String profileId, SoundProfile profile) {
synchronized (mLock) {
for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
// TODO: filter callback record
@@ -129,7 +129,7 @@ public final class MediaQualityManager {
}
}
@Override
- public void onSoundProfileRemoved(long profileId, SoundProfile profile) {
+ public void onSoundProfileRemoved(String profileId, SoundProfile profile) {
synchronized (mLock) {
for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
// TODO: filter callback record
@@ -137,6 +137,24 @@ public final class MediaQualityManager {
}
}
}
+ @Override
+ public void onParamCapabilitiesChanged(String profileId, List<ParamCapability> caps) {
+ synchronized (mLock) {
+ for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+ // TODO: filter callback record
+ record.postParamCapabilitiesChanged(profileId, caps);
+ }
+ }
+ }
+ @Override
+ public void onError(int err) {
+ synchronized (mLock) {
+ for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+ // TODO: filter callback record
+ record.postError(err);
+ }
+ }
+ }
};
IAmbientBacklightCallback abCallback = new IAmbientBacklightCallback.Stub() {
@Override
@@ -331,14 +349,17 @@ public final class MediaQualityManager {
/**
- * Gets sound profile by given profile ID.
- * @return the corresponding sound profile if available; {@code null} if the ID doesn't
- * exist or the profile is not accessible to the caller.
+ * Gets sound profile by given profile type and name.
+ *
+ * @return the corresponding sound profile if available; {@code null} if the name doesn't
+ * exist.
* @hide
*/
- public SoundProfile getSoundProfileById(String profileId) {
+ @Nullable
+ public SoundProfile getSoundProfile(
+ @SoundProfile.ProfileType int type, @NonNull String name) {
try {
- return mService.getSoundProfileById(profileId);
+ return mService.getSoundProfile(type, name);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -349,8 +370,9 @@ public final class MediaQualityManager {
* @SystemApi gets profiles that available to the given package
* @hide
*/
+ @NonNull
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
- public List<SoundProfile> getSoundProfilesByPackage(String packageName) {
+ public List<SoundProfile> getSoundProfilesByPackage(@NonNull String packageName) {
try {
return mService.getSoundProfilesByPackage(packageName);
} catch (RemoteException e) {
@@ -362,6 +384,7 @@ public final class MediaQualityManager {
* Gets profiles that available to the caller package
* @hide
*/
+ @NonNull
public List<SoundProfile> getAvailableSoundProfiles() {
try {
return mService.getAvailableSoundProfiles();
@@ -371,9 +394,12 @@ public final class MediaQualityManager {
}
/**
- * @SystemApi all stored sound profiles of all packages
+ * @SystemApi Gets all package names whose sound profiles are available.
+ *
+ * @see #getSoundProfilesByPackage(String)
* @hide
*/
+ @NonNull
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
public List<String> getSoundProfilePackageNames() {
try {
@@ -387,12 +413,13 @@ public final class MediaQualityManager {
/**
* Creates a sound profile and store it in the system.
*
- * @return the stored profile with an assigned profile ID.
+ * <p>If the profile is created successfully,
+ * {@link SoundProfileCallback#onSoundProfileAdded(long, SoundProfile)} is invoked.
* @hide
*/
- public SoundProfile createSoundProfile(SoundProfile sp) {
+ public void createSoundProfile(@NonNull SoundProfile sp) {
try {
- return mService.createSoundProfile(sp);
+ mService.createSoundProfile(sp);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -403,7 +430,7 @@ public final class MediaQualityManager {
* Updates an existing sound profile and store it in the system.
* @hide
*/
- public void updateSoundProfile(String profileId, SoundProfile sp) {
+ public void updateSoundProfile(@NonNull String profileId, @NonNull SoundProfile sp) {
try {
mService.updateSoundProfile(profileId, sp);
} catch (RemoteException e) {
@@ -416,7 +443,7 @@ public final class MediaQualityManager {
* Removes a sound profile from the system.
* @hide
*/
- public void removeSoundProfile(String profileId) {
+ public void removeSoundProfile(@NonNull String profileId) {
try {
mService.removeSoundProfile(profileId);
} catch (RemoteException e) {
@@ -468,6 +495,36 @@ public final class MediaQualityManager {
}
/**
+ * Gets the allowlist of packages that can create and removed sound profiles
+ *
+ * @see #createSoundProfile(SoundProfile)
+ * @see #removeSoundProfile(String)
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+ @NonNull
+ public List<String> getSoundProfileAllowList() {
+ try {
+ return mService.getSoundProfileAllowList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the allowlist of packages that can create and removed sound profiles
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+ public void setSoundProfileAllowList(@NonNull List<String> packageNames) {
+ try {
+ mService.setSoundProfileAllowList(packageNames);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns {@code true} if media quality HAL is implemented; {@code false} otherwise.
* @hide
*/
@@ -567,6 +624,7 @@ public final class MediaQualityManager {
/**
* Registers a {@link AmbientBacklightCallback}.
+ * @hide
*/
public void registerAmbientBacklightCallback(
@NonNull @CallbackExecutor Executor executor,
@@ -580,6 +638,7 @@ public final class MediaQualityManager {
/**
* Unregisters the existing {@link AmbientBacklightCallback}.
+ * @hide
*/
public void unregisterAmbientBacklightCallback(
@NonNull final AmbientBacklightCallback callback) {
@@ -600,6 +659,7 @@ public final class MediaQualityManager {
* Set the ambient backlight settings.
*
* @param settings The settings to use for the backlight detector.
+ * @hide
*/
public void setAmbientBacklightSettings(
@NonNull AmbientBacklightSettings settings) {
@@ -615,6 +675,7 @@ public final class MediaQualityManager {
* Enables or disables the ambient backlight detection.
*
* @param enabled {@code true} to enable, {@code false} to disable.
+ * @hide
*/
public void setAmbientBacklightEnabled(boolean enabled) {
try {
@@ -698,7 +759,7 @@ public final class MediaQualityManager {
return mCallback;
}
- public void postSoundProfileAdded(final long id, SoundProfile profile) {
+ public void postSoundProfileAdded(final String id, SoundProfile profile) {
mExecutor.execute(new Runnable() {
@Override
@@ -708,7 +769,7 @@ public final class MediaQualityManager {
});
}
- public void postSoundProfileUpdated(final long id, SoundProfile profile) {
+ public void postSoundProfileUpdated(final String id, SoundProfile profile) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
@@ -717,7 +778,7 @@ public final class MediaQualityManager {
});
}
- public void postSoundProfileRemoved(final long id, SoundProfile profile) {
+ public void postSoundProfileRemoved(final String id, SoundProfile profile) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
@@ -725,6 +786,24 @@ public final class MediaQualityManager {
}
});
}
+
+ public void postParamCapabilitiesChanged(final String id, List<ParamCapability> caps) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onParamCapabilitiesChanged(id, caps);
+ }
+ });
+ }
+
+ public void postError(int error) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onError(error);
+ }
+ });
+ }
}
private static final class AmbientBacklightCallbackRecord {
@@ -801,12 +880,13 @@ public final class MediaQualityManager {
* This is invoked when parameter capabilities has been changed due to status changes of the
* content.
*
- * @param profileId the ID of the profile used by the media content.
+ * @param profileId the ID of the profile used by the media content. {@code null} if there
+ * is no associated profile
* @param updatedCaps the updated capabilities.
* @hide
*/
public void onParamCapabilitiesChanged(
- @NonNull String profileId, @NonNull List<ParamCapability> updatedCaps) {
+ @Nullable String profileId, @NonNull List<ParamCapability> updatedCaps) {
}
}
@@ -816,29 +896,64 @@ public final class MediaQualityManager {
*/
public abstract static class SoundProfileCallback {
/**
+ * This is invoked when a sound profile has been added.
+ *
+ * @param profileId the ID of the profile.
+ * @param profile the newly added profile.
* @hide
*/
- public void onSoundProfileAdded(long id, SoundProfile profile) {
+ public void onSoundProfileAdded(
+ @NonNull String profileId, @NonNull SoundProfile profile) {
}
+
/**
+ * This is invoked when a sound profile has been updated.
+ *
+ * @param profileId the ID of the profile.
+ * @param profile the profile with updated info.
* @hide
*/
- public void onSoundProfileUpdated(long id, SoundProfile profile) {
+ public void onSoundProfileUpdated(
+ @NonNull String profileId, @NonNull SoundProfile profile) {
}
+
/**
+ * This is invoked when a sound profile has been removed.
+ *
+ * @param profileId the ID of the profile.
+ * @param profile the removed profile.
* @hide
*/
- public void onSoundProfileRemoved(long id, SoundProfile profile) {
+ public void onSoundProfileRemoved(
+ @NonNull String profileId, @NonNull SoundProfile profile) {
}
+
/**
+ * This is invoked when an issue has occurred.
+ *
+ * @param errorCode the error code
* @hide
*/
- public void onError(int errorCode) {
+ public void onError(@SoundProfile.ErrorCode int errorCode) {
+ }
+
+ /**
+ * This is invoked when parameter capabilities has been changed due to status changes of the
+ * content.
+ *
+ * @param profileId the ID of the profile used by the media content. {@code null} if there
+ * is no associated profile
+ * @param updatedCaps the updated capabilities.
+ * @hide
+ */
+ public void onParamCapabilitiesChanged(
+ @Nullable String profileId, @NonNull List<ParamCapability> updatedCaps) {
}
}
/**
* Callback used to monitor status of ambient backlight.
+ * @hide
*/
public abstract static class AmbientBacklightCallback {
/**
diff --git a/media/java/android/media/quality/SoundProfile.java b/media/java/android/media/quality/SoundProfile.java
index 20d117bf15cf..de93afe4316f 100644
--- a/media/java/android/media/quality/SoundProfile.java
+++ b/media/java/android/media/quality/SoundProfile.java
@@ -17,54 +17,119 @@
package android.media.quality;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.media.tv.TvInputInfo;
import android.media.tv.flags.Flags;
-import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresPermission;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
+ * Profile for sound quality.
* @hide
*/
@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
public class SoundProfile implements Parcelable {
@Nullable
- private Long mId;
+ private String mId;
+ private final int mType;
@NonNull
private final String mName;
@Nullable
private final String mInputId;
- @Nullable
+ @NonNull
private final String mPackageName;
@NonNull
- private final Bundle mParams;
+ private final PersistableBundle mParams;
- protected SoundProfile(Parcel in) {
- if (in.readByte() == 0) {
- mId = null;
- } else {
- mId = in.readLong();
- }
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = "TYPE_", value = {
+ TYPE_SYSTEM,
+ TYPE_APPLICATION})
+ public @interface ProfileType {}
+
+ /**
+ * System profile type.
+ *
+ * <p>A profile of system type is managed by the system, and readable to the package returned by
+ * {@link #getPackageName()}.
+ */
+ public static final int TYPE_SYSTEM = 1;
+ /**
+ * Application profile type.
+ *
+ * <p>A profile of application type is managed by the package returned by
+ * {@link #getPackageName()}.
+ */
+ public static final int TYPE_APPLICATION = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = "ERROR_", value = {
+ ERROR_UNKNOWN,
+ ERROR_NO_PERMISSION,
+ ERROR_DUPLICATE,
+ ERROR_INVALID_ARGUMENT,
+ ERROR_NOT_ALLOWLISTED
+ })
+ public @interface ErrorCode {}
+
+ /**
+ * Error code for unknown errors.
+ */
+ public static final int ERROR_UNKNOWN = 0;
+
+ /**
+ * Error code for missing necessary permission to handle the profiles.
+ */
+ public static final int ERROR_NO_PERMISSION = 1;
+
+ /**
+ * Error code for creating a profile with existing profile type and name.
+ *
+ * @see #getProfileType()
+ * @see #getName()
+ */
+ public static final int ERROR_DUPLICATE = 2;
+
+ /**
+ * Error code for invalid argument.
+ */
+ public static final int ERROR_INVALID_ARGUMENT = 3;
+
+ /**
+ * Error code for the case when an operation requires an allowlist but the caller is not in the
+ * list.
+ *
+ * @see MediaQualityManager#getSoundProfileAllowList()
+ */
+ public static final int ERROR_NOT_ALLOWLISTED = 4;
+
+ protected SoundProfile(@NonNull Parcel in) {
+ mId = in.readString();
+ mType = in.readInt();
mName = in.readString();
mInputId = in.readString();
mPackageName = in.readString();
- mParams = in.readBundle();
+ mParams = in.readPersistableBundle();
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
- if (mId == null) {
- dest.writeByte((byte) 0);
- } else {
- dest.writeByte((byte) 1);
- dest.writeLong(mId);
- }
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mId);
+ dest.writeInt(mType);
dest.writeString(mName);
dest.writeString(mInputId);
dest.writeString(mPackageName);
- dest.writeBundle(mParams);
+ dest.writePersistableBundle(mParams);
}
@Override
@@ -72,6 +137,7 @@ public class SoundProfile implements Parcelable {
return 0;
}
+ @NonNull
public static final Creator<SoundProfile> CREATOR = new Creator<SoundProfile>() {
@Override
public SoundProfile createFromParcel(Parcel in) {
@@ -91,93 +157,164 @@ public class SoundProfile implements Parcelable {
* @hide
*/
public SoundProfile(
- @Nullable Long id,
+ @Nullable String id,
+ int type,
@NonNull String name,
@Nullable String inputId,
- @Nullable String packageName,
- @NonNull Bundle params) {
+ @NonNull String packageName,
+ @NonNull PersistableBundle params) {
this.mId = id;
+ this.mType = type;
this.mName = name;
- com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name);
this.mInputId = inputId;
this.mPackageName = packageName;
this.mParams = params;
}
+ /**
+ * Gets profile ID.
+ *
+ * <p>A profile ID is a globally unique ID generated and assigned by the system. For profile
+ * objects retrieved from system (e.g {@link MediaQualityManager#getAvailableSoundProfiles()})
+ * this profile ID is non-null; For profiles built locally with {@link Builder}, it's
+ * {@code null}.
+ *
+ * @return the unique profile ID; {@code null} if the profile is built locally with
+ * {@link Builder}.
+ */
@Nullable
- public Long getProfileId() {
+ public String getProfileId() {
return mId;
}
+ /**
+ * Only used by system to assign the ID.
+ * @hide
+ */
+ public void setProfileId(String id) {
+ mId = id;
+ }
+
+ /**
+ * Gets profile type.
+ */
+ @ProfileType
+ public int getProfileType() {
+ return mType;
+ }
+
+ /**
+ * Gets the profile name.
+ */
@NonNull
public String getName() {
return mName;
}
+ /**
+ * Gets the input ID if the profile is for a TV input.
+ *
+ * @return the corresponding TV input ID; {@code null} if the profile is not associated with a
+ * TV input.
+ *
+ * @see TvInputInfo#getId()
+ */
@Nullable
public String getInputId() {
return mInputId;
}
+ /**
+ * Gets the package name of this profile.
+ *
+ * <p>The package name defines the user of a profile. Only this specific package and system app
+ * can access to this profile.
+ *
+ * @return the package name; {@code null} if the profile is built locally using
+ * {@link Builder} and the package is not set.
+ */
@Nullable
public String getPackageName() {
return mPackageName;
}
+
+ /**
+ * Gets the parameters of this profile.
+ *
+ * <p>The keys of commonly used parameters can be found in
+ * {@link MediaQualityContract.SoundQuality}.
+ */
@NonNull
- public Bundle getParameters() {
- return new Bundle(mParams);
+ public PersistableBundle getParameters() {
+ return new PersistableBundle(mParams);
}
/**
* A builder for {@link SoundProfile}
+ * @hide
*/
public static class Builder {
@Nullable
- private Long mId;
+ private String mId;
+ private int mType = TYPE_APPLICATION;
@NonNull
private String mName;
@Nullable
private String mInputId;
- @Nullable
+ @NonNull
private String mPackageName;
@NonNull
- private Bundle mParams;
+ private PersistableBundle mParams;
/**
* Creates a new Builder.
- *
- * @hide
*/
public Builder(@NonNull String name) {
mName = name;
- com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name);
}
/**
- * Copy constructor.
- *
- * @hide
+ * Copy constructor of builder.
*/
public Builder(@NonNull SoundProfile p) {
mId = null; // ID needs to be reset
+ mType = p.getProfileType();
mName = p.getName();
mPackageName = p.getPackageName();
mInputId = p.getInputId();
+ mParams = p.getParameters();
}
/**
- * Sets profile ID.
- * @hide using by MediaQualityService
+ * Only used by system to assign the ID.
+ * @hide
*/
@NonNull
- public Builder setProfileId(@Nullable Long id) {
+ public Builder setProfileId(@Nullable String id) {
mId = id;
return this;
}
/**
+ * Sets profile type.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+ @NonNull
+ public Builder setProfileType(@ProfileType int value) {
+ mType = value;
+ return this;
+ }
+
+ /**
* Sets input ID.
+ *
+ * @see SoundProfile#getInputId()
+ *
+ * @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
@NonNull
public Builder setInputId(@NonNull String value) {
mInputId = value;
@@ -186,7 +323,12 @@ public class SoundProfile implements Parcelable {
/**
* Sets package name of the profile.
+ *
+ * @see SoundProfile#getPackageName()
+ *
+ * @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
@NonNull
public Builder setPackageName(@NonNull String value) {
mPackageName = value;
@@ -195,12 +337,15 @@ public class SoundProfile implements Parcelable {
/**
* Sets profile parameters.
+ *
+ * @see SoundProfile#getParameters()
*/
@NonNull
- public Builder setParameters(@NonNull Bundle params) {
- mParams = new Bundle(params);
+ public Builder setParameters(@NonNull PersistableBundle params) {
+ mParams = new PersistableBundle(params);
return this;
}
+
/**
* Builds the instance.
*/
@@ -209,6 +354,7 @@ public class SoundProfile implements Parcelable {
SoundProfile o = new SoundProfile(
mId,
+ mType,
mName,
mInputId,
mPackageName,
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index dd2a534676d8..ff0279fef99e 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -240,6 +240,38 @@ public class TvAdView extends ViewGroup {
}
}
+ /**
+ * Controls whether the TvAdView's surface is placed on top of other regular surface views in
+ * the window (but still behind the window itself).
+ *
+ * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
+ *
+ * @param isMediaOverlay {@code true} to be on top of another regular surface, {@code false}
+ * otherwise.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
+ public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+ if (mSurfaceView != null) {
+ mSurfaceView.setZOrderOnTop(false);
+ mSurfaceView.setZOrderMediaOverlay(isMediaOverlay);
+ }
+ }
+
+ /**
+ * Controls whether the TvAdView's surface is placed on top of its window.
+ *
+ * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
+ *
+ * @param onTop {@code true} to be on top of its window, {@code false} otherwise.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
+ public void setZOrderOnTop(boolean onTop) {
+ if (mSurfaceView != null) {
+ mSurfaceView.setZOrderMediaOverlay(false);
+ mSurfaceView.setZOrderOnTop(onTop);
+ }
+ }
+
private void resetSurfaceView() {
if (mSurfaceView != null) {
mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
diff --git a/media/java/android/media/tv/extension/oad/IOadUpdateInterface.aidl b/media/java/android/media/tv/extension/oad/IOadUpdateInterface.aidl
new file mode 100644
index 000000000000..2a2e71a53993
--- /dev/null
+++ b/media/java/android/media/tv/extension/oad/IOadUpdateInterface.aidl
@@ -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 android.media.tv.extension.oad;
+
+/**
+ * @hide
+ */
+interface IOadUpdateInterface {
+ // Enable or disable the OAD function.
+ void setOadStatus(boolean enable);
+ // Get status of OAD function.
+ boolean getOadStatus();
+ // Start OAD scan of all frequency in the program list.
+ void startScan();
+ // Stop OAD scan of all frequency in the program list.
+ void stopScan();
+ // Start OAD detect for the current channel.
+ void startDetect();
+ // Stop OAD detect for the current channel.
+ void stopDetect();
+ // Start OAD download after it has been detected or scanned.
+ void startDownload();
+ // Stop OAD download.
+ void stopDownload();
+ // Retrieves current OAD software version.
+ int getSoftwareVersion();
+}
diff --git a/core/java/android/text/ClientFlags.java b/media/java/android/media/tv/extension/rating/IDownloadableRatingTableMonitor.aidl
index ca887646b3aa..bf1a385f05ae 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/media/java/android/media/tv/extension/rating/IDownloadableRatingTableMonitor.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,14 @@
* limitations under the License.
*/
-package android.text;
+package android.media.tv.extension.rating;
+
+import android.os.Bundle;
/**
- * An aconfig feature flags that can be accessible from application process without
- * ContentProvider IPCs.
- *
- * When you add new flags, you have to add flag string to {@link TextFlags#TEXT_ACONFIGS_FLAGS}.
- *
- * TODO(nona): Remove this class.
* @hide
*/
-public class ClientFlags {
+interface IDownloadableRatingTableMonitor {
+ // Get RRT rating info on downloadable rating data
+ Bundle[] getTable();
}
diff --git a/media/java/android/media/tv/extension/rating/IPmtRatingInterface.aidl b/media/java/android/media/tv/extension/rating/IPmtRatingInterface.aidl
new file mode 100644
index 000000000000..06cac3d0d411
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IPmtRatingInterface.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.rating;
+
+import android.media.tv.extension.rating.IPmtRatingListener;
+
+/**
+ * @hide
+ */
+interface IPmtRatingInterface {
+ // Get Pmt rating information.
+ String getPmtRating(String sessionToken);
+ // Register a listener for pmt rating updates.
+ void addPmtRatingListener(String clientToken, in IPmtRatingListener listener);
+ // Remove the previously added IPmtRatingListener.
+ void removePmtRatingListener(in IPmtRatingListener listener);
+}
diff --git a/media/java/android/media/tv/extension/rating/IPmtRatingListener.aidl b/media/java/android/media/tv/extension/rating/IPmtRatingListener.aidl
new file mode 100644
index 000000000000..d88ae9425f8c
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IPmtRatingListener.aidl
@@ -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 android.media.tv.extension.rating;
+
+/**
+ * @hide
+ */
+oneway interface IPmtRatingListener {
+ void onPmtRatingChanged(String sessionToken, String newTvContentRating);
+}
diff --git a/media/java/android/media/tv/extension/rating/IProgramRatingInfo.aidl b/media/java/android/media/tv/extension/rating/IProgramRatingInfo.aidl
new file mode 100644
index 000000000000..a490491d7acc
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IProgramRatingInfo.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.rating;
+
+import android.media.tv.extension.rating.IProgramRatingInfoListener;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IProgramRatingInfo {
+ // Register a listener to receive notifications when ProgramRatingInfo is updated.
+ void addProgramRatingInfoListener(String clientToken, in IProgramRatingInfoListener listener);
+ // Remove a listener for ProgramRatingInfo update notifications.
+ void removeProgramRatingInfoListener(in IProgramRatingInfoListener listener);
+ // Get ProgramRatingInfo that may only be obtained when viewing.
+ Bundle getProgramRatingInfo(String sessionToken);
+}
diff --git a/media/java/android/media/tv/extension/rating/IProgramRatingInfoListener.aidl b/media/java/android/media/tv/extension/rating/IProgramRatingInfoListener.aidl
new file mode 100644
index 000000000000..6777cd3035d8
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IProgramRatingInfoListener.aidl
@@ -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 android.media.tv.extension.rating;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IProgramRatingInfoListener {
+ void onProgramInfoChanged(String sessionToken,in Bundle changedProgramInfo);
+}
diff --git a/media/java/android/media/tv/extension/rating/IRatingInterface.aidl b/media/java/android/media/tv/extension/rating/IRatingInterface.aidl
new file mode 100644
index 000000000000..d68fe763ef28
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IRatingInterface.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.rating;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IRatingInterface {
+ // Get RRT rating information
+ Bundle getRRTRatingInfo();
+ // Set RRT rating information when user select
+ boolean setRRTRatingInfo(in Bundle param);
+ // Reset RRT5 to clear information
+ boolean setResetRrt5();
+}
diff --git a/media/java/android/media/tv/extension/rating/IVbiRatingInterface.aidl b/media/java/android/media/tv/extension/rating/IVbiRatingInterface.aidl
new file mode 100644
index 000000000000..bad40676e8aa
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IVbiRatingInterface.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.rating;
+
+import android.media.tv.extension.rating.IVbiRatingListener;
+
+/**
+ * @hide
+ */
+interface IVbiRatingInterface {
+ // Get Vbi rating.
+ String getVbiRating(String sessionToken);
+ // Register a listener for Vbi rating updates.
+ void addVbiRatingListener(String clientToken, in IVbiRatingListener listener);
+ // Remove the previously added VbiRatingListener.
+ void removeVbiRatingListener(in IVbiRatingListener listener);
+}
diff --git a/media/java/android/media/tv/extension/rating/IVbiRatingListener.aidl b/media/java/android/media/tv/extension/rating/IVbiRatingListener.aidl
new file mode 100644
index 000000000000..36d523f97613
--- /dev/null
+++ b/media/java/android/media/tv/extension/rating/IVbiRatingListener.aidl
@@ -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 android.media.tv.extension.rating;
+
+/**
+ * @hide
+ */
+oneway interface IVbiRatingListener {
+ void onVbiRatingChanged(String sessionToken, String newTvContentRating);
+}
diff --git a/media/java/android/media/tv/extension/time/IBroadcastTime.aidl b/media/java/android/media/tv/extension/time/IBroadcastTime.aidl
new file mode 100644
index 000000000000..123d00f9faf4
--- /dev/null
+++ b/media/java/android/media/tv/extension/time/IBroadcastTime.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.time;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IBroadcastTime {
+ long getUtcTime();
+ long getLocalTime();
+ Bundle getTimeZoneInfo();
+ long getUtcTimePerStream(String SessionToken);
+ long getLocalTimePerStream(String SessionToken);
+} \ No newline at end of file
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 64416525441b..4b832aee49c5 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -2,6 +2,22 @@ package: "android.media.tv.flags"
container: "system"
flag {
+ name: "enable_le_audio_broadcast_ui"
+ is_exported: true
+ namespace: "media_tv"
+ description: "Enable Broadcast UI for LE Audio on TV."
+ bug: "378732734"
+}
+
+flag {
+ name: "enable_le_audio_unicast_ui"
+ is_exported: true
+ namespace: "media_tv"
+ description: "Enable Unicast UI for LE Audio on TV."
+ bug: "378732734"
+}
+
+flag {
name: "broadcast_visibility_types"
is_exported: true
namespace: "media_tv"
@@ -77,11 +93,19 @@ flag {
name: "set_resource_holder_retain"
is_exported: true
namespace: "media_tv"
- description : "Feature flag to add setResourceHolderRetain api to MediaCas and Tuner JAVA."
+ description: "Feature flag to add setResourceHolderRetain api to MediaCas and Tuner JAVA."
bug: "372973197"
}
flag {
+ name: "mediacas_update_client_profile_priority"
+ is_exported: true
+ namespace: "media_tv"
+ description: "Feature flag to add updateResourcePriority api to MediaCas"
+ bug: "372971241"
+}
+
+flag {
name: "apply_picture_profiles"
is_exported: true
namespace: "media_tv"
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 635572d12cc5..9e9699ff6bc6 100644
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -271,6 +271,38 @@ public class TvInteractiveAppView extends ViewGroup {
}
}
+ /**
+ * Controls whether the TvInteractiveAppView's surface is placed on top of other regular surface
+ * views in the window (but still behind the window itself).
+ *
+ * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
+ *
+ * @param isMediaOverlay {@code true} to be on top of another regular surface, {@code false}
+ * otherwise.
+ */
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+ public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+ if (mSurfaceView != null) {
+ mSurfaceView.setZOrderOnTop(false);
+ mSurfaceView.setZOrderMediaOverlay(isMediaOverlay);
+ }
+ }
+
+ /**
+ * Controls whether the TvInterActiveAppView's surface is placed on top of its window.
+ *
+ * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
+ *
+ * @param onTop {@code true} to be on top of its window, {@code false} otherwise.
+ */
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+ public void setZOrderOnTop(boolean onTop) {
+ if (mSurfaceView != null) {
+ mSurfaceView.setZOrderMediaOverlay(false);
+ mSurfaceView.setZOrderOnTop(onTop);
+ }
+ }
+
private void resetSurfaceView() {
if (mSurfaceView != null) {
mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index ac85ab7f6a6e..88c1c434cc0d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -257,6 +257,16 @@ public class CameraBinderTest extends AndroidTestCase {
public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) {
// TODO Auto-generated method stub
}
+
+ /*
+ * (non-Javadoc)
+ * @see android.hardware.camera2.ICameraDeviceCallbacks#onClientSharedAccessPriorityChanged
+ */
+ @Override
+ public void onClientSharedAccessPriorityChanged(boolean primaryClient) {
+ // TODO Auto-generated method stub
+ }
+
}
@SmallTest
@@ -276,7 +286,7 @@ public class CameraBinderTest extends AndroidTestCase {
0 /*oomScoreOffset*/,
getContext().getApplicationInfo().targetSdkVersion,
ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution,
- DEVICE_POLICY_DEFAULT);
+ DEVICE_POLICY_DEFAULT, false/*sharedMode*/);
assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
Log.v(TAG, String.format("Camera %s connected", cameraId));
@@ -320,6 +330,13 @@ public class CameraBinderTest extends AndroidTestCase {
Log.v(TAG, String.format("Camera " + cameraId + " torch strength level changed to "
+ torchStrength ));
}
+ @Override
+ public void onCameraOpenedInSharedMode(String cameraId, String clientPackageName,
+ int deviceId, boolean primaryClient) {
+ Log.v(TAG, "Camera " + cameraId + " is opened in shared mode by "
+ + "client package " + clientPackageName + " as primary client="
+ + primaryClient);
+ }
}
/**
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 35ad924cee74..3758c515c1a5 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -175,6 +175,15 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) {
// TODO Auto-generated method stub
}
+
+ /**
+ * (non-Javadoc)
+ * @see android.hardware.camera2.ICameraDeviceCallbacks#onClientSharedAccessPriorityChanged
+ */
+ @Override
+ public void onClientSharedAccessPriorityChanged(boolean primaryClient) {
+ // TODO Auto-generated method stub
+ }
}
class IsMetadataNotEmpty implements ArgumentMatcher<CameraMetadataNative> {
@@ -250,7 +259,8 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId,
/*oomScoreOffset*/0, getContext().getApplicationInfo().targetSdkVersion,
- ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution, DEVICE_POLICY_DEFAULT);
+ ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution, DEVICE_POLICY_DEFAULT,
+ /*sharedMode*/false);
assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser);
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index a0460572abfc..2d1fbf9e7f66 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -361,6 +361,8 @@ LIBANDROID {
APerformanceHint_setThreads; # introduced=UpsideDownCake
APerformanceHint_setPreferPowerEfficiency; # introduced=VanillaIceCream
APerformanceHint_reportActualWorkDuration2; # introduced=VanillaIceCream
+ APerformanceHint_notifyWorkloadIncrease; # introduced=36
+ APerformanceHint_notifyWorkloadReset; # introduced=36
AWorkDuration_create; # introduced=VanillaIceCream
AWorkDuration_release; # introduced=VanillaIceCream
AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream
@@ -379,6 +381,8 @@ LIBANDROID_PLATFORM {
APerformanceHint_getThreadIds;
APerformanceHint_createSessionInternal;
APerformanceHint_setUseFMQForTesting;
+ APerformanceHint_getRateLimiterPropertiesForTesting;
+ APerformanceHint_setUseNewLoadHintBehaviorForTesting;
extern "C++" {
ASurfaceControl_registerSurfaceStatsListener*;
ASurfaceControl_unregisterSurfaceStatsListener*;
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 15f77cebf3ba..e2fa94dd39bb 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -33,12 +33,14 @@
#include <android/performance_hint.h>
#include <android/trace.h>
#include <android_os.h>
+#include <cutils/trace.h>
#include <fmq/AidlMessageQueue.h>
#include <inttypes.h>
#include <performance_hint_private.h>
#include <utils/SystemClock.h>
#include <chrono>
+#include <format>
#include <future>
#include <set>
#include <utility>
@@ -63,6 +65,22 @@ struct APerformanceHintSession;
constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
struct AWorkDuration : public hal::WorkDuration {};
+// A pair of values that determine the behavior of the
+// load hint rate limiter, to only allow "X hints every Y seconds"
+constexpr double kLoadHintInterval = std::chrono::nanoseconds(2s).count();
+constexpr double kMaxLoadHintsPerInterval = 20;
+constexpr double kReplenishRate = kMaxLoadHintsPerInterval / kLoadHintInterval;
+bool kForceNewHintBehavior = false;
+
+template <class T>
+constexpr int32_t enum_size() {
+ return static_cast<int32_t>(*(ndk::enum_range<T>().end() - 1)) + 1;
+}
+
+bool useNewLoadHintBehavior() {
+ return android::os::adpf_use_load_hints() || kForceNewHintBehavior;
+}
+
// Shared lock for the whole PerformanceHintManager and sessions
static std::mutex sHintMutex = std::mutex{};
class FMQWrapper {
@@ -76,7 +94,8 @@ public:
hal::WorkDuration* durations, size_t count) REQUIRES(sHintMutex);
bool updateTargetWorkDuration(std::optional<hal::SessionConfig>& config,
int64_t targetDurationNanos) REQUIRES(sHintMutex);
- bool sendHint(std::optional<hal::SessionConfig>& config, SessionHint hint) REQUIRES(sHintMutex);
+ bool sendHints(std::optional<hal::SessionConfig>& config, std::vector<hal::SessionHint>& hint,
+ int64_t now) REQUIRES(sHintMutex);
bool setMode(std::optional<hal::SessionConfig>& config, hal::SessionMode, bool enabled)
REQUIRES(sHintMutex);
void setToken(ndk::SpAIBinder& token);
@@ -86,10 +105,11 @@ public:
private:
template <HalChannelMessageContents::Tag T, bool urgent = false,
class C = HalChannelMessageContents::_at<T>>
- bool sendMessages(std::optional<hal::SessionConfig>& config, C* message, size_t count = 1)
- REQUIRES(sHintMutex);
+ bool sendMessages(std::optional<hal::SessionConfig>& config, C* message, size_t count = 1,
+ int64_t now = ::android::uptimeNanos()) REQUIRES(sHintMutex);
template <HalChannelMessageContents::Tag T, class C = HalChannelMessageContents::_at<T>>
- void writeBuffer(C* message, hal::SessionConfig& config, size_t count) REQUIRES(sHintMutex);
+ void writeBuffer(C* message, hal::SessionConfig& config, size_t count, int64_t now)
+ REQUIRES(sHintMutex);
bool isActiveLocked() REQUIRES(sHintMutex);
bool updatePersistentTransaction() REQUIRES(sHintMutex);
@@ -120,6 +140,7 @@ public:
hal::SessionTag tag = hal::SessionTag::APP);
int64_t getPreferredRateNanos() const;
FMQWrapper& getFMQWrapper();
+ bool canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) REQUIRES(sHintMutex);
private:
// Necessary to create an empty binder object
@@ -138,6 +159,8 @@ private:
ndk::SpAIBinder mToken;
const int64_t mPreferredRateNanos;
FMQWrapper mFMQWrapper;
+ double mHintBudget = kMaxLoadHintsPerInterval;
+ int64_t mLastBudgetReplenish = 0;
};
struct APerformanceHintSession {
@@ -151,7 +174,9 @@ public:
int updateTargetWorkDuration(int64_t targetDurationNanos);
int reportActualWorkDuration(int64_t actualDurationNanos);
- int sendHint(SessionHint hint);
+ int sendHints(std::vector<hal::SessionHint>& hints, int64_t now, const char* debugName);
+ int notifyWorkloadIncrease(bool cpu, bool gpu, const char* debugName);
+ int notifyWorkloadReset(bool cpu, bool gpu, const char* debugName);
int setThreads(const int32_t* threadIds, size_t size);
int getThreadIds(int32_t* const threadIds, size_t* size);
int setPreferPowerEfficiency(bool enabled);
@@ -173,6 +198,8 @@ private:
// Last target hit timestamp
int64_t mLastTargetMetTimestamp GUARDED_BY(sHintMutex);
// Last hint reported from sendHint indexed by hint value
+ // This is only used by the old rate limiter impl and is replaced
+ // with the new rate limiter under a flag
std::vector<int64_t> mLastHintSentTimestamp GUARDED_BY(sHintMutex);
// Cached samples
std::vector<hal::WorkDuration> mActualWorkDurations GUARDED_BY(sHintMutex);
@@ -255,6 +282,21 @@ APerformanceHintManager* APerformanceHintManager::create(std::shared_ptr<IHintMa
return new APerformanceHintManager(manager, preferredRateNanos);
}
+bool APerformanceHintManager::canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) {
+ mHintBudget =
+ std::max(kMaxLoadHintsPerInterval,
+ mHintBudget +
+ static_cast<double>(now - mLastBudgetReplenish) * kReplenishRate);
+ mLastBudgetReplenish = now;
+
+ // If this youngest timestamp isn't older than the timeout time, we can't send
+ if (hints.size() > mHintBudget) {
+ return false;
+ }
+ mHintBudget -= hints.size();
+ return true;
+}
+
APerformanceHintSession* APerformanceHintManager::createSession(
const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos,
hal::SessionTag tag) {
@@ -292,9 +334,7 @@ FMQWrapper& APerformanceHintManager::getFMQWrapper() {
// ===================================== APerformanceHintSession implementation
-constexpr int kNumEnums =
- ndk::enum_range<hal::SessionHint>().end() - ndk::enum_range<hal::SessionHint>().begin();
-
+constexpr int kNumEnums = enum_size<hal::SessionHint>();
APerformanceHintSession::APerformanceHintSession(std::shared_ptr<IHintManager> hintManager,
std::shared_ptr<IHintSession> session,
int64_t preferredRateNanos,
@@ -361,31 +401,83 @@ int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNano
return reportActualWorkDurationInternal(static_cast<AWorkDuration*>(&workDuration));
}
-int APerformanceHintSession::sendHint(SessionHint hint) {
+int APerformanceHintSession::sendHints(std::vector<hal::SessionHint>& hints, int64_t now,
+ const char*) {
std::scoped_lock lock(sHintMutex);
- if (hint < 0 || hint >= static_cast<int32_t>(mLastHintSentTimestamp.size())) {
- ALOGE("%s: invalid session hint %d", __FUNCTION__, hint);
+ if (hints.empty()) {
return EINVAL;
}
- int64_t now = uptimeNanos();
+ for (auto&& hint : hints) {
+ if (static_cast<int32_t>(hint) < 0 || static_cast<int32_t>(hint) >= kNumEnums) {
+ ALOGE("%s: invalid session hint %d", __FUNCTION__, hint);
+ return EINVAL;
+ }
+ }
- // Limit sendHint to a pre-detemined rate for safety
- if (now < (mLastHintSentTimestamp[hint] + SEND_HINT_TIMEOUT)) {
- return 0;
+ if (useNewLoadHintBehavior()) {
+ if (!APerformanceHintManager::getInstance()->canSendLoadHints(hints, now)) {
+ return EBUSY;
+ }
+ }
+ // keep old rate limiter behavior for legacy flag
+ else {
+ for (auto&& hint : hints) {
+ if (now < (mLastHintSentTimestamp[static_cast<int32_t>(hint)] + SEND_HINT_TIMEOUT)) {
+ return EBUSY;
+ }
+ }
}
- if (!getFMQ().sendHint(mSessionConfig, hint)) {
- ndk::ScopedAStatus ret = mHintSession->sendHint(hint);
+ if (!getFMQ().sendHints(mSessionConfig, hints, now)) {
+ for (auto&& hint : hints) {
+ ndk::ScopedAStatus ret = mHintSession->sendHint(static_cast<int32_t>(hint));
- if (!ret.isOk()) {
- ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.getMessage());
- return EPIPE;
+ if (!ret.isOk()) {
+ ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.getMessage());
+ return EPIPE;
+ }
+ }
+ }
+
+ if (!useNewLoadHintBehavior()) {
+ for (auto&& hint : hints) {
+ mLastHintSentTimestamp[static_cast<int32_t>(hint)] = now;
}
}
- mLastHintSentTimestamp[hint] = now;
+
+ if (ATrace_isEnabled()) {
+ ATRACE_INSTANT("Sending load hint");
+ }
+
return 0;
}
+int APerformanceHintSession::notifyWorkloadIncrease(bool cpu, bool gpu, const char* debugName) {
+ std::vector<hal::SessionHint> hints(2);
+ hints.clear();
+ if (cpu) {
+ hints.push_back(hal::SessionHint::CPU_LOAD_UP);
+ }
+ if (gpu) {
+ hints.push_back(hal::SessionHint::GPU_LOAD_UP);
+ }
+ int64_t now = ::android::uptimeNanos();
+ return sendHints(hints, now, debugName);
+}
+
+int APerformanceHintSession::notifyWorkloadReset(bool cpu, bool gpu, const char* debugName) {
+ std::vector<hal::SessionHint> hints(2);
+ hints.clear();
+ if (cpu) {
+ hints.push_back(hal::SessionHint::CPU_LOAD_RESET);
+ }
+ if (gpu) {
+ hints.push_back(hal::SessionHint::GPU_LOAD_RESET);
+ }
+ int64_t now = ::android::uptimeNanos();
+ return sendHints(hints, now, debugName);
+}
+
int APerformanceHintSession::setThreads(const int32_t* threadIds, size_t size) {
if (size == 0) {
ALOGE("%s: the list of thread ids must not be empty.", __FUNCTION__);
@@ -565,24 +657,25 @@ void FMQWrapper::stopChannel(IHintManager* manager) {
}
template <HalChannelMessageContents::Tag T, class C>
-void FMQWrapper::writeBuffer(C* message, hal::SessionConfig& config, size_t) {
- new (mFmqTransaction.getSlot(0)) hal::ChannelMessage{
- .sessionID = static_cast<int32_t>(config.id),
- .timeStampNanos = ::android::uptimeNanos(),
- .data = HalChannelMessageContents::make<T, C>(std::move(*message)),
- };
+void FMQWrapper::writeBuffer(C* message, hal::SessionConfig& config, size_t count, int64_t now) {
+ for (size_t i = 0; i < count; ++i) {
+ new (mFmqTransaction.getSlot(i)) hal::ChannelMessage{
+ .sessionID = static_cast<int32_t>(config.id),
+ .timeStampNanos = now,
+ .data = HalChannelMessageContents::make<T, C>(std::move(*(message + i))),
+ };
+ }
}
template <>
void FMQWrapper::writeBuffer<HalChannelMessageContents::workDuration>(hal::WorkDuration* messages,
hal::SessionConfig& config,
- size_t count) {
+ size_t count, int64_t now) {
for (size_t i = 0; i < count; ++i) {
hal::WorkDuration& message = messages[i];
new (mFmqTransaction.getSlot(i)) hal::ChannelMessage{
.sessionID = static_cast<int32_t>(config.id),
- .timeStampNanos =
- (i == count - 1) ? ::android::uptimeNanos() : message.timeStampNanos,
+ .timeStampNanos = (i == count - 1) ? now : message.timeStampNanos,
.data = HalChannelMessageContents::make<HalChannelMessageContents::workDuration,
hal::WorkDurationFixedV1>({
.durationNanos = message.cpuDurationNanos,
@@ -595,7 +688,8 @@ void FMQWrapper::writeBuffer<HalChannelMessageContents::workDuration>(hal::WorkD
}
template <HalChannelMessageContents::Tag T, bool urgent, class C>
-bool FMQWrapper::sendMessages(std::optional<hal::SessionConfig>& config, C* message, size_t count) {
+bool FMQWrapper::sendMessages(std::optional<hal::SessionConfig>& config, C* message, size_t count,
+ int64_t now) {
if (!isActiveLocked() || !config.has_value() || mCorrupted) {
return false;
}
@@ -609,7 +703,7 @@ bool FMQWrapper::sendMessages(std::optional<hal::SessionConfig>& config, C* mess
return false;
}
}
- writeBuffer<T, C>(message, *config, count);
+ writeBuffer<T, C>(message, *config, count, now);
mQueue->commitWrite(count);
mEventFlag->wake(mWriteMask);
// Re-create the persistent transaction after writing
@@ -641,10 +735,9 @@ bool FMQWrapper::updateTargetWorkDuration(std::optional<hal::SessionConfig>& con
return sendMessages<HalChannelMessageContents::targetDuration>(config, &targetDurationNanos);
}
-bool FMQWrapper::sendHint(std::optional<hal::SessionConfig>& config, SessionHint hint) {
- return sendMessages<HalChannelMessageContents::hint>(config,
- reinterpret_cast<hal::SessionHint*>(
- &hint));
+bool FMQWrapper::sendHints(std::optional<hal::SessionConfig>& config,
+ std::vector<hal::SessionHint>& hints, int64_t now) {
+ return sendMessages<HalChannelMessageContents::hint>(config, hints.data(), hints.size(), now);
}
bool FMQWrapper::setMode(std::optional<hal::SessionConfig>& config, hal::SessionMode mode,
@@ -758,7 +851,9 @@ void APerformanceHint_closeSession(APerformanceHintSession* session) {
int APerformanceHint_sendHint(APerformanceHintSession* session, SessionHint hint) {
VALIDATE_PTR(session)
- return session->sendHint(hint);
+ std::vector<hal::SessionHint> hints{static_cast<hal::SessionHint>(hint)};
+ int64_t now = ::android::uptimeNanos();
+ return session->sendHints(hints, now, "HWUI hint");
}
int APerformanceHint_setThreads(APerformanceHintSession* session, const pid_t* threadIds,
@@ -791,6 +886,26 @@ int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session,
return session->reportActualWorkDuration(workDurationPtr);
}
+int APerformanceHint_notifyWorkloadIncrease(APerformanceHintSession* session, bool cpu, bool gpu,
+ const char* debugName) {
+ VALIDATE_PTR(session)
+ VALIDATE_PTR(debugName)
+ if (!useNewLoadHintBehavior()) {
+ return ENOTSUP;
+ }
+ return session->notifyWorkloadIncrease(cpu, gpu, debugName);
+}
+
+int APerformanceHint_notifyWorkloadReset(APerformanceHintSession* session, bool cpu, bool gpu,
+ const char* debugName) {
+ VALIDATE_PTR(session)
+ VALIDATE_PTR(debugName)
+ if (!useNewLoadHintBehavior()) {
+ return ENOTSUP;
+ }
+ return session->notifyWorkloadReset(cpu, gpu, debugName);
+}
+
AWorkDuration* AWorkDuration_create() {
return new AWorkDuration();
}
@@ -838,3 +953,13 @@ void APerformanceHint_setIHintManagerForTesting(void* iManager) {
void APerformanceHint_setUseFMQForTesting(bool enabled) {
gForceFMQEnabled = enabled;
}
+
+void APerformanceHint_getRateLimiterPropertiesForTesting(int32_t* maxLoadHintsPerInterval,
+ int64_t* loadHintInterval) {
+ *maxLoadHintsPerInterval = kMaxLoadHintsPerInterval;
+ *loadHintInterval = kLoadHintInterval;
+}
+
+void APerformanceHint_setUseNewLoadHintBehaviorForTesting(bool newBehavior) {
+ kForceNewHintBehavior = newBehavior;
+}
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 9de3a6f525e6..f707a0e9b0b2 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -66,6 +66,18 @@ public:
std::optional<hal::ChannelConfig>* _aidl_return),
(override));
MOCK_METHOD(ScopedAStatus, closeSessionChannel, (), (override));
+ MOCK_METHOD(ScopedAStatus, getCpuHeadroom,
+ (const ::aidl::android::os::CpuHeadroomParamsInternal& in_params,
+ std::vector<float>* _aidl_return),
+ (override));
+ MOCK_METHOD(ScopedAStatus, getCpuHeadroomMinIntervalMillis, (int64_t* _aidl_return),
+ (override));
+ MOCK_METHOD(ScopedAStatus, getGpuHeadroom,
+ (const ::aidl::android::os::GpuHeadroomParamsInternal& in_params,
+ float* _aidl_return),
+ (override));
+ MOCK_METHOD(ScopedAStatus, getGpuHeadroomMinIntervalMillis, (int64_t* _aidl_return),
+ (override));
MOCK_METHOD(SpAIBinder, asBinder, (), (override));
MOCK_METHOD(bool, isRemote, (), (override));
};
@@ -90,7 +102,10 @@ class PerformanceHintTest : public Test {
public:
void SetUp() override {
mMockIHintManager = ndk::SharedRefBase::make<NiceMock<MockIHintManager>>();
+ APerformanceHint_getRateLimiterPropertiesForTesting(&mMaxLoadHintsPerInterval,
+ &mLoadHintInterval);
APerformanceHint_setIHintManagerForTesting(&mMockIHintManager);
+ APerformanceHint_setUseNewLoadHintBehaviorForTesting(true);
}
void TearDown() override {
@@ -176,6 +191,9 @@ public:
int kMockQueueSize = 20;
bool mUsingFMQ = false;
+ int32_t mMaxLoadHintsPerInterval;
+ int64_t mLoadHintInterval;
+
template <HalChannelMessageContents::Tag T, class C = HalChannelMessageContents::_at<T>>
void expectToReadFromFmq(C expected) {
hal::ChannelMessage readData;
@@ -218,7 +236,6 @@ TEST_F(PerformanceHintTest, TestSession) {
EXPECT_CALL(*mMockSession, reportActualWorkDuration2(_)).Times(Exactly(1));
result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos);
EXPECT_EQ(0, result);
-
result = APerformanceHint_updateTargetWorkDuration(session, -1L);
EXPECT_EQ(EINVAL, result);
result = APerformanceHint_reportActualWorkDuration(session, -1L);
@@ -228,18 +245,28 @@ TEST_F(PerformanceHintTest, TestSession) {
EXPECT_CALL(*mMockSession, sendHint(Eq(hintId))).Times(Exactly(1));
result = APerformanceHint_sendHint(session, hintId);
EXPECT_EQ(0, result);
- usleep(110000); // Sleep for longer than the update timeout.
- EXPECT_CALL(*mMockSession, sendHint(Eq(hintId))).Times(Exactly(1));
- result = APerformanceHint_sendHint(session, hintId);
+ EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::CPU_LOAD_UP))).Times(Exactly(1));
+ result = APerformanceHint_notifyWorkloadIncrease(session, true, false, "Test hint");
EXPECT_EQ(0, result);
- // Expect to get rate limited if we try to send faster than the limiter allows
- EXPECT_CALL(*mMockSession, sendHint(Eq(hintId))).Times(Exactly(0));
- result = APerformanceHint_sendHint(session, hintId);
+ EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::CPU_LOAD_RESET))).Times(Exactly(1));
+ EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::GPU_LOAD_RESET))).Times(Exactly(1));
+ result = APerformanceHint_notifyWorkloadReset(session, true, true, "Test hint");
EXPECT_EQ(0, result);
result = APerformanceHint_sendHint(session, static_cast<SessionHint>(-1));
EXPECT_EQ(EINVAL, result);
+ Mock::VerifyAndClearExpectations(mMockSession.get());
+ for (int i = 0; i < mMaxLoadHintsPerInterval; ++i) {
+ APerformanceHint_sendHint(session, hintId);
+ }
+
+ // Expect to get rate limited if we try to send faster than the limiter allows
+ EXPECT_CALL(*mMockSession, sendHint(_)).Times(Exactly(0));
+ result = APerformanceHint_notifyWorkloadIncrease(session, true, true, "Test hint");
+ EXPECT_EQ(result, EBUSY);
+ EXPECT_CALL(*mMockSession, sendHint(_)).Times(Exactly(0));
+ result = APerformanceHint_notifyWorkloadReset(session, true, true, "Test hint");
EXPECT_CALL(*mMockSession, close()).Times(Exactly(1));
APerformanceHint_closeSession(session);
}
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 23dd9b7aee37..4180710534c3 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -45,8 +45,10 @@ cc_library_shared {
header_libs: [
"jni_headers",
+ "native_headers",
"libhwui_internal_headers",
],
+ export_header_lib_headers: ["native_headers"],
static_libs: [
"libarect",
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index c25f77b12116..f587660cae5b 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -57,6 +57,7 @@ package android.nfc {
@FlaggedApi("android.nfc.nfc_oem_extension") public final class NfcOemExtension {
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int forceRoutingTableCommit();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.nfc.RoutingStatus getRoutingStatus();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.List<android.nfc.NfcRoutingTableEntry> getRoutingTable();
@@ -73,6 +74,9 @@ package android.nfc {
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void triggerInitialization();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterCallback(@NonNull android.nfc.NfcOemExtension.Callback);
+ field public static final int COMMIT_ROUTING_STATUS_FAILED = 3; // 0x3
+ field public static final int COMMIT_ROUTING_STATUS_FAILED_UPDATE_IN_PROGRESS = 6; // 0x6
+ field public static final int COMMIT_ROUTING_STATUS_OK = 0; // 0x0
field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int DISABLE = 0; // 0x0
field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_DEFAULT = 1; // 0x1
field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_EE = 3; // 0x3
@@ -89,13 +93,14 @@ package android.nfc {
method public void onBootFinished(int);
method public void onBootStarted();
method public void onCardEmulationActivated(boolean);
- method public void onDisable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onDisableFinished(int);
+ method public void onDisableRequested(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onDisableStarted();
method public void onEeListenActivated(boolean);
- method public void onEnable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onEnableFinished(int);
+ method public void onEnableRequested(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onEnableStarted();
+ method public void onExtractOemPackages(@NonNull android.nfc.NdefMessage, @NonNull java.util.function.Consumer<java.util.List<java.lang.String>>);
method public void onGetOemAppSearchIntent(@NonNull java.util.List<java.lang.String>, @NonNull java.util.function.Consumer<android.content.Intent>);
method public void onHceEventReceived(int);
method public void onLaunchHceAppChooserActivity(@NonNull String, @NonNull java.util.List<android.nfc.cardemulation.ApduServiceInfo>, @NonNull android.content.ComponentName, @NonNull String);
@@ -106,7 +111,7 @@ package android.nfc {
method public void onReaderOptionChanged(boolean);
method public void onRfDiscoveryStarted(boolean);
method public void onRfFieldActivated(boolean);
- method public void onRoutingChanged();
+ method public void onRoutingChanged(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onRoutingTableFull();
method public void onStateUpdated(int);
method public void onTagConnected(boolean);
@@ -183,6 +188,12 @@ package android.nfc.cardemulation {
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void overrideRoutingTable(@NonNull android.app.Activity, int, int);
method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void recoverRoutingTable(@NonNull android.app.Activity);
+ method @FlaggedApi("android.nfc.nfc_set_service_enabled_for_category_other") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setServiceEnabledForCategoryOther(@NonNull android.content.ComponentName, boolean);
+ field @FlaggedApi("android.nfc.nfc_set_service_enabled_for_category_other") public static final int SET_SERVICE_ENABLED_STATUS_FAILURE_ALREADY_SET = 3; // 0x3
+ field @FlaggedApi("android.nfc.nfc_set_service_enabled_for_category_other") public static final int SET_SERVICE_ENABLED_STATUS_FAILURE_FEATURE_UNSUPPORTED = 1; // 0x1
+ field @FlaggedApi("android.nfc.nfc_set_service_enabled_for_category_other") public static final int SET_SERVICE_ENABLED_STATUS_FAILURE_INVALID_SERVICE = 2; // 0x2
+ field @FlaggedApi("android.nfc.nfc_set_service_enabled_for_category_other") public static final int SET_SERVICE_ENABLED_STATUS_FAILURE_UNKNOWN_ERROR = 4; // 0x4
+ field @FlaggedApi("android.nfc.nfc_set_service_enabled_for_category_other") public static final int SET_SERVICE_ENABLED_STATUS_OK = 0; // 0x0
}
}
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 40fd0683f465..31514a09adad 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -120,4 +120,5 @@ interface INfcAdapter
boolean isTagPresent();
List<Entry> getRoutingTableEntryList();
void indicateDataMigration(boolean inProgress, String pkg);
+ int commitRouting();
}
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 5e2e92d958a4..633d8bfbbb67 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -47,7 +47,7 @@ interface INfcCardEmulation
boolean unsetPreferredService();
boolean supportsAidPrefixRegistration();
ApduServiceInfo getPreferredPaymentService(int userHandle);
- boolean setServiceEnabledForCategoryOther(int userHandle, in ComponentName app, boolean status);
+ int setServiceEnabledForCategoryOther(int userHandle, in ComponentName app, boolean status);
boolean isDefaultPaymentRegistered();
void overrideRoutingTable(int userHandle, String protocol, String technology, in String pkg);
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index fb793b024288..1a21c0bae413 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -41,7 +41,7 @@ interface INfcOemExtensionCallback {
void onEnableFinished(int status);
void onDisableFinished(int status);
void onTagDispatch(in ResultReceiver isSkipped);
- void onRoutingChanged();
+ void onRoutingChanged(in ResultReceiver isSkipped);
void onHceEventReceived(int action);
void onReaderOptionChanged(boolean enabled);
void onCardEmulationActivated(boolean isActivated);
@@ -54,4 +54,5 @@ interface INfcOemExtensionCallback {
void onLaunchHceTapAgainActivity(in ApduServiceInfo service, in String category);
void onRoutingTableFull();
void onLogEventNotified(in OemLogItems item);
+ void onExtractOemPackages(in NdefMessage message, in ResultReceiver packageReceiver);
}
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 57ee981caf9c..326ca6449c53 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -23,11 +23,11 @@ import static android.nfc.cardemulation.CardEmulation.routeIntToString;
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.DurationMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.Context;
@@ -194,6 +194,30 @@ public final class NfcOemExtension {
public @interface StatusCode {}
/**
+ * Routing commit succeeded.
+ */
+ public static final int COMMIT_ROUTING_STATUS_OK = 0;
+ /**
+ * Routing commit failed.
+ */
+ public static final int COMMIT_ROUTING_STATUS_FAILED = 3;
+ /**
+ * Routing commit failed due to the update is in progress.
+ */
+ public static final int COMMIT_ROUTING_STATUS_FAILED_UPDATE_IN_PROGRESS = 6;
+
+ /**
+ * Status codes returned when calling {@link #forceRoutingTableCommit()}
+ * @hide
+ */
+ @IntDef(prefix = "COMMIT_ROUTING_STATUS_", value = {
+ COMMIT_ROUTING_STATUS_OK,
+ COMMIT_ROUTING_STATUS_FAILED,
+ COMMIT_ROUTING_STATUS_FAILED_UPDATE_IN_PROGRESS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CommitRoutingStatusCode {}
+ /**
* Interface for Oem extensions for NFC.
*/
public interface Callback {
@@ -233,8 +257,7 @@ public final class NfcOemExtension {
* {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}.
* false if NFC cannot be enabled at this time.
*/
- @SuppressLint("MethodNameTense")
- void onEnable(@NonNull Consumer<Boolean> isAllowed);
+ void onEnableRequested(@NonNull Consumer<Boolean> isAllowed);
/**
* Method to check if Nfc is allowed to be disabled by OEMs.
* @param isAllowed The {@link Consumer} to be completed. If disabling NFC is allowed,
@@ -242,7 +265,7 @@ public final class NfcOemExtension {
* {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}.
* false if NFC cannot be disabled at this time.
*/
- void onDisable(@NonNull Consumer<Boolean> isAllowed);
+ void onDisableRequested(@NonNull Consumer<Boolean> isAllowed);
/**
* Callback to indicate that Nfc starts to boot.
@@ -255,7 +278,7 @@ public final class NfcOemExtension {
void onEnableStarted();
/**
- * Callback to indicate that Nfc starts to enable.
+ * Callback to indicate that Nfc starts to disable.
*/
void onDisableStarted();
@@ -287,8 +310,12 @@ public final class NfcOemExtension {
/**
* Notifies routing configuration is changed.
+ * @param isCommitRoutingSkipped The {@link Consumer} to be
+ * completed. If routing commit should be skipped,
+ * the {@link Consumer#accept(Object)} should be called with
+ * {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}.
*/
- void onRoutingChanged();
+ void onRoutingChanged(@NonNull Consumer<Boolean> isCommitRoutingSkipped);
/**
* API to activate start stop cpu boost on hce event.
@@ -405,6 +432,19 @@ public final class NfcOemExtension {
* @param item the log items that contains log information of NFC event.
*/
void onLogEventNotified(@NonNull OemLogItems item);
+
+ /**
+ * Callback to to extract OEM defined packages from given NDEF message when
+ * a NFC tag is detected. These are used to handle NFC tags encoded with a
+ * proprietary format for storing app name (Android native app format).
+ *
+ * @param message NDEF message containing OEM package names
+ * @param packageConsumer The {@link Consumer} to be completed.
+ * The {@link Consumer#accept(Object)} should be called with
+ * the list of package names.
+ */
+ void onExtractOemPackages(@NonNull NdefMessage message,
+ @NonNull Consumer<List<String>> packageConsumer);
}
@@ -605,12 +645,12 @@ public final class NfcOemExtension {
/**
* Pauses NFC tag reader mode polling for a {@code timeoutInMs} millisecond.
* In case of {@code timeoutInMs} is zero or invalid polling will be stopped indefinitely
- * use {@link #resumePolling() to resume the polling.
- * @param timeoutInMs the pause polling duration in millisecond
+ * use {@link #resumePolling()} to resume the polling.
+ * @param timeoutInMs the pause polling duration in millisecond, ranging from 0 to 40000.
*/
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- public void pausePolling(int timeoutInMs) {
+ public void pausePolling(@DurationMillisLong int timeoutInMs) {
NfcAdapter.callService(() -> NfcAdapter.sService.pausePolling(timeoutInMs));
}
@@ -741,6 +781,18 @@ public final class NfcOemExtension {
return result;
}
+ /**
+ * API to force a routing table commit.
+ * @return a {@link StatusCode} to indicate if commit routing succeeded or not
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @CommitRoutingStatusCode
+ public int forceRoutingTableCommit() {
+ return NfcAdapter.callServiceReturn(
+ () -> NfcAdapter.sService.commitRouting(), COMMIT_ROUTING_STATUS_FAILED);
+ }
+
private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
@Override
@@ -799,13 +851,13 @@ public final class NfcOemExtension {
public void onEnable(ResultReceiver isAllowed) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(
- new ReceiverWrapper<>(isAllowed), cb::onEnable, ex));
+ new ReceiverWrapper<>(isAllowed), cb::onEnableRequested, ex));
}
@Override
public void onDisable(ResultReceiver isAllowed) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(
- new ReceiverWrapper<>(isAllowed), cb::onDisable, ex));
+ new ReceiverWrapper<>(isAllowed), cb::onDisableRequested, ex));
}
@Override
public void onBootStarted() throws RemoteException {
@@ -844,9 +896,10 @@ public final class NfcOemExtension {
new ReceiverWrapper<>(isSkipped), cb::onTagDispatch, ex));
}
@Override
- public void onRoutingChanged() throws RemoteException {
+ public void onRoutingChanged(ResultReceiver isSkipped) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
- handleVoidCallback(null, (Object input) -> cb.onRoutingChanged(), ex));
+ handleVoidCallback(
+ new ReceiverWrapper<>(isSkipped), cb::onRoutingChanged, ex));
}
@Override
public void onHceEventReceived(int action) throws RemoteException {
@@ -925,6 +978,15 @@ public final class NfcOemExtension {
handleVoidCallback(item, cb::onLogEventNotified, ex));
}
+ @Override
+ public void onExtractOemPackages(NdefMessage message, ResultReceiver packageConsumer)
+ throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoid2ArgCallback(message,
+ new ReceiverWrapper<>(packageConsumer),
+ cb::onExtractOemPackages, ex));
+ }
+
private <T> void handleVoidCallback(
T input, Consumer<T> callbackMethod, Executor executor) {
synchronized (mLock) {
@@ -1035,8 +1097,14 @@ public final class NfcOemExtension {
Bundle bundle = new Bundle();
bundle.putParcelable("intent", (Intent) result);
mResultReceiver.send(0, bundle);
+ } else if (result instanceof List<?> list) {
+ if (list.stream().allMatch(String.class::isInstance)) {
+ Bundle bundle = new Bundle();
+ bundle.putStringArray("packageNames",
+ list.stream().map(pkg -> (String) pkg).toArray(String[]::new));
+ mResultReceiver.send(0, bundle);
+ }
}
-
}
@Override
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index eb28c3b9c930..891752475824 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -185,6 +185,65 @@ public final class CardEmulation {
@FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET = -1;
+ /**
+ * Status code returned when {@link #setServiceEnabledForCategoryOther(ComponentName, boolean)}
+ * succeeded.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_SET_SERVICE_ENABLED_FOR_CATEGORY_OTHER)
+ public static final int SET_SERVICE_ENABLED_STATUS_OK = 0;
+
+ /**
+ * Status code returned when {@link #setServiceEnabledForCategoryOther(ComponentName, boolean)}
+ * failed due to the unsupported feature.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_SET_SERVICE_ENABLED_FOR_CATEGORY_OTHER)
+ public static final int SET_SERVICE_ENABLED_STATUS_FAILURE_FEATURE_UNSUPPORTED = 1;
+
+ /**
+ * Status code returned when {@link #setServiceEnabledForCategoryOther(ComponentName, boolean)}
+ * failed due to the invalid service.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_SET_SERVICE_ENABLED_FOR_CATEGORY_OTHER)
+ public static final int SET_SERVICE_ENABLED_STATUS_FAILURE_INVALID_SERVICE = 2;
+
+ /**
+ * Status code returned when {@link #setServiceEnabledForCategoryOther(ComponentName, boolean)}
+ * failed due to the service is already set to the requested status.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_SET_SERVICE_ENABLED_FOR_CATEGORY_OTHER)
+ public static final int SET_SERVICE_ENABLED_STATUS_FAILURE_ALREADY_SET = 3;
+
+ /**
+ * Status code returned when {@link #setServiceEnabledForCategoryOther(ComponentName, boolean)}
+ * failed due to unknown error.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_SET_SERVICE_ENABLED_FOR_CATEGORY_OTHER)
+ public static final int SET_SERVICE_ENABLED_STATUS_FAILURE_UNKNOWN_ERROR = 4;
+
+ /**
+ * Status code returned by {@link #setServiceEnabledForCategoryOther(ComponentName, boolean)}
+ * @hide
+ */
+ @IntDef(prefix = "SET_SERVICE_ENABLED_STATUS_", value = {
+ SET_SERVICE_ENABLED_STATUS_OK,
+ SET_SERVICE_ENABLED_STATUS_FAILURE_FEATURE_UNSUPPORTED,
+ SET_SERVICE_ENABLED_STATUS_FAILURE_INVALID_SERVICE,
+ SET_SERVICE_ENABLED_STATUS_FAILURE_ALREADY_SET,
+ SET_SERVICE_ENABLED_STATUS_FAILURE_UNKNOWN_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SetServiceEnabledStatusCode {}
+
static boolean sIsInitialized = false;
static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
static INfcCardEmulation sService;
@@ -883,22 +942,24 @@ public final class CardEmulation {
}
/**
- * Allows to set or unset preferred service (category other) to avoid AID Collision.
+ * Allows to set or unset preferred service (category other) to avoid AID Collision. The user
+ * should use corresponding context using {@link Context#createContextAsUser(UserHandle, int)}
*
* @param service The ComponentName of the service
* @param status true to enable, false to disable
- * @param userId the user handle of the user whose information is being requested.
- * @return set service for the category and true if service is already set return false.
+ * @return true if preferred service is successfully set or unset, otherwise return false.
*
* @hide
*/
- public boolean setServiceEnabledForCategoryOther(ComponentName service, boolean status,
- int userId) {
- if (service == null) {
- throw new NullPointerException("activity or service or category is null");
- }
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_SET_SERVICE_ENABLED_FOR_CATEGORY_OTHER)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @SetServiceEnabledStatusCode
+ public int setServiceEnabledForCategoryOther(@NonNull ComponentName service,
+ boolean status) {
return callServiceReturn(() ->
- sService.setServiceEnabledForCategoryOther(userId, service, status), false);
+ sService.setServiceEnabledForCategoryOther(mContext.getUser().getIdentifier(),
+ service, status), SET_SERVICE_ENABLED_STATUS_FAILURE_UNKNOWN_ERROR);
}
/** @hide */
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 34f020012d3c..8a37aa28cf9d 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -173,3 +173,11 @@ flag {
description: "Share wallet role routing priority with associated services"
bug: "366243361"
}
+
+flag {
+ name: "nfc_set_service_enabled_for_category_other"
+ is_exported: true
+ namespace: "nfc"
+ description: "Enable set service enabled for category other"
+ bug: "338157113"
+}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
index 5ceee6d09978..088bef230374 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
@@ -22,11 +22,13 @@ import com.android.settingslib.graph.proto.PreferenceGraphProto
import com.android.settingslib.ipc.ApiHandler
import com.android.settingslib.ipc.MessageCodec
import com.android.settingslib.metadata.PreferenceScreenRegistry
+import com.android.settingslib.preference.PreferenceScreenProvider
import java.util.Locale
/** API to get preference graph. */
-abstract class GetPreferenceGraphApiHandler :
- ApiHandler<GetPreferenceGraphRequest, PreferenceGraphProto> {
+abstract class GetPreferenceGraphApiHandler(
+ private val preferenceScreenProviders: Set<Class<out PreferenceScreenProvider>>
+) : ApiHandler<GetPreferenceGraphRequest, PreferenceGraphProto> {
override val requestCodec: MessageCodec<GetPreferenceGraphRequest>
get() = GetPreferenceGraphRequestCodec
@@ -40,14 +42,16 @@ abstract class GetPreferenceGraphApiHandler :
callingUid: Int,
request: GetPreferenceGraphRequest,
): PreferenceGraphProto {
- val builderRequest =
- if (request.screenKeys.isEmpty()) {
- val keys = PreferenceScreenRegistry.preferenceScreens.keys
- GetPreferenceGraphRequest(keys, request.visitedScreens, request.locale)
- } else {
- request
+ val builder = PreferenceGraphBuilder.of(application, request)
+ if (request.screenKeys.isEmpty()) {
+ for (key in PreferenceScreenRegistry.preferenceScreens.keys) {
+ builder.addPreferenceScreenFromRegistry(key)
}
- return PreferenceGraphBuilder.of(application, builderRequest).build()
+ for (provider in preferenceScreenProviders) {
+ builder.addPreferenceScreenProvider(provider)
+ }
+ }
+ return builder.build()
}
}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index 2256bb38dd2c..6760e72be4a6 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -133,7 +133,7 @@ private constructor(private val context: Context, private val request: GetPrefer
null
}
- private suspend fun addPreferenceScreenFromRegistry(key: String): Boolean {
+ suspend fun addPreferenceScreenFromRegistry(key: String): Boolean {
val metadata = PreferenceScreenRegistry[key] ?: return false
return addPreferenceScreenMetadata(metadata)
}
@@ -146,7 +146,7 @@ private constructor(private val context: Context, private val request: GetPrefer
}
}
- private suspend fun addPreferenceScreenProvider(activityClass: Class<*>) {
+ suspend fun addPreferenceScreenProvider(activityClass: Class<*>) {
Log.d(TAG, "add $activityClass")
createPreferenceScreen { activityClass.newInstance() }
?.let {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
index 6e4db1d90484..7cfce0d85cd4 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
@@ -22,6 +22,7 @@ import androidx.annotation.IntDef
import com.android.settingslib.graph.proto.PreferenceValueProto
import com.android.settingslib.ipc.ApiDescriptor
import com.android.settingslib.ipc.ApiHandler
+import com.android.settingslib.ipc.ApiPermissionChecker
import com.android.settingslib.ipc.IntMessageCodec
import com.android.settingslib.ipc.MessageCodec
import com.android.settingslib.metadata.BooleanValue
@@ -45,7 +46,11 @@ data class PreferenceSetterRequest(
PreferenceSetterResult.OK,
PreferenceSetterResult.UNSUPPORTED,
PreferenceSetterResult.DISABLED,
+ PreferenceSetterResult.RESTRICTED,
PreferenceSetterResult.UNAVAILABLE,
+ PreferenceSetterResult.REQUIRE_APP_PERMISSION,
+ PreferenceSetterResult.REQUIRE_USER_AGREEMENT,
+ PreferenceSetterResult.DISALLOW,
PreferenceSetterResult.INVALID_REQUEST,
PreferenceSetterResult.INTERNAL_ERROR,
)
@@ -87,14 +92,17 @@ class PreferenceSetterApiDescriptor(override val id: Int) :
}
/** Preference setter API implementation. */
-class PreferenceSetterApiHandler(override val id: Int) : ApiHandler<PreferenceSetterRequest, Int> {
+class PreferenceSetterApiHandler(
+ override val id: Int,
+ private val permissionChecker: ApiPermissionChecker<PreferenceSetterRequest>,
+) : ApiHandler<PreferenceSetterRequest, Int> {
override fun hasPermission(
application: Application,
myUid: Int,
callingUid: Int,
request: PreferenceSetterRequest,
- ): Boolean = true
+ ) = permissionChecker.hasPermission(application, myUid, callingUid, request)
override suspend fun invoke(
application: Application,
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoConverters.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoConverters.kt
index d9b9590f60e2..1bda277f2018 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoConverters.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoConverters.kt
@@ -16,8 +16,10 @@
package com.android.settingslib.graph
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.net.Uri
import android.os.Bundle
import com.android.settingslib.graph.proto.BundleProto
import com.android.settingslib.graph.proto.BundleProto.BundleValue
@@ -42,6 +44,20 @@ fun Intent.toProto(): IntentProto = intentProto {
this@toProto.type?.let { mimeType = it }
}
+fun IntentProto.toIntent(): Intent? {
+ if (!hasComponent()) return null
+ val componentName = ComponentName.unflattenFromString(component) ?: return null
+ val intent = Intent()
+ intent.component = componentName
+ if (hasAction()) intent.action = action
+ if (hasData()) intent.data = Uri.parse(data)
+ if (hasPkg()) intent.`package` = pkg
+ if (hasFlags()) intent.flags = flags
+ if (hasExtras()) intent.putExtras(extras.toBundle())
+ if (hasMimeType()) intent.setType(mimeType)
+ return intent
+}
+
fun Bundle.toProto(): BundleProto = bundleProto {
fun toProto(value: Any): BundleValue = bundleValueProto {
when (value) {
@@ -61,14 +77,18 @@ fun Bundle.toProto(): BundleProto = bundleProto {
}
}
-fun BundleValue.stringify(): String =
- when {
- hasBooleanValue() -> "$valueCase"
- hasBytesValue() -> "$bytesValue"
- hasIntValue() -> "$intValue"
- hasLongValue() -> "$longValue"
- hasStringValue() -> stringValue
- hasDoubleValue() -> "$doubleValue"
- hasBundleValue() -> "$bundleValue"
- else -> "Unknown"
+fun BundleProto.toBundle(): Bundle =
+ Bundle().apply {
+ for ((key, value) in valuesMap) {
+ when {
+ value.hasBooleanValue() -> putBoolean(key, value.booleanValue)
+ value.hasBytesValue() -> putByteArray(key, value.bytesValue.toByteArray())
+ value.hasIntValue() -> putInt(key, value.intValue)
+ value.hasLongValue() -> putLong(key, value.longValue)
+ value.hasStringValue() -> putString(key, value.stringValue)
+ value.hasDoubleValue() -> putDouble(key, value.doubleValue)
+ value.hasBundleValue() -> putBundle(key, value.bundleValue.toBundle())
+ else -> throw IllegalArgumentException("Unknown type: ${value.javaClass} $value")
+ }
+ }
}
diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt
index 802141dae7ec..4febd89a7da4 100644
--- a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt
+++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt
@@ -56,6 +56,27 @@ interface ApiDescriptor<Request, Response> {
val responseCodec: MessageCodec<Response>
}
+/** Permission checker for api. */
+fun interface ApiPermissionChecker<R> {
+ /**
+ * Returns if the request is permitted.
+ *
+ * @param application application context
+ * @param myUid uid of current process
+ * @param callingUid uid of peer process
+ * @param request API request
+ * @return `false` if permission is denied, otherwise `true`
+ */
+ fun hasPermission(application: Application, myUid: Int, callingUid: Int, request: R): Boolean
+
+ companion object {
+ private val ALWAYS_ALLOW = ApiPermissionChecker<Any> { _, _, _, _ -> true }
+
+ @Suppress("UNCHECKED_CAST")
+ fun <T> alwaysAllow(): ApiPermissionChecker<T> = ALWAYS_ALLOW as ApiPermissionChecker<T>
+ }
+}
+
/**
* Handler of API.
*
@@ -64,18 +85,8 @@ interface ApiDescriptor<Request, Response> {
*
* The implementation must be threadsafe.
*/
-interface ApiHandler<Request, Response> : ApiDescriptor<Request, Response> {
- /**
- * Returns if the request is permitted.
- *
- * @return `false` if permission is denied, otherwise `true`
- */
- fun hasPermission(
- application: Application,
- myUid: Int,
- callingUid: Int,
- request: Request,
- ): Boolean
+interface ApiHandler<Request, Response> :
+ ApiDescriptor<Request, Response>, ApiPermissionChecker<Request> {
/**
* Invokes the API.
diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt
index 7ffefed239a4..ef907e17d824 100644
--- a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt
+++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt
@@ -320,6 +320,11 @@ constructor(
}
}
+ override fun onNullBinding(name: ComponentName) {
+ Log.i(TAG, "onNullBinding $name")
+ close(ClientBindServiceException(null))
+ }
+
internal open fun drainPendingRequests() {
disposableHandle = null
if (pendingRequests.isEmpty()) {
diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt
index 6e38df11156f..1823ba641775 100644
--- a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt
+++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt
@@ -19,9 +19,14 @@ package com.android.settingslib.service
import android.app.Application
import com.android.settingslib.graph.GetPreferenceGraphApiHandler
import com.android.settingslib.graph.GetPreferenceGraphRequest
+import com.android.settingslib.ipc.ApiPermissionChecker
+import com.android.settingslib.preference.PreferenceScreenProvider
/** Api to get preference graph. */
-internal class PreferenceGraphApi : GetPreferenceGraphApiHandler() {
+internal class PreferenceGraphApi(
+ preferenceScreenProviders: Set<Class<out PreferenceScreenProvider>>,
+ private val permissionChecker: ApiPermissionChecker<GetPreferenceGraphRequest>,
+) : GetPreferenceGraphApiHandler(preferenceScreenProviders) {
override val id: Int
get() = API_GET_PREFERENCE_GRAPH
@@ -31,5 +36,5 @@ internal class PreferenceGraphApi : GetPreferenceGraphApiHandler() {
myUid: Int,
callingUid: Int,
request: GetPreferenceGraphRequest,
- ) = true
+ ) = permissionChecker.hasPermission(application, myUid, callingUid, request)
}
diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt
index 8ebb14522c3d..ed748bb58a9a 100644
--- a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt
+++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt
@@ -16,10 +16,14 @@
package com.android.settingslib.service
+import com.android.settingslib.graph.GetPreferenceGraphRequest
import com.android.settingslib.graph.PreferenceSetterApiHandler
+import com.android.settingslib.graph.PreferenceSetterRequest
import com.android.settingslib.ipc.ApiHandler
+import com.android.settingslib.ipc.ApiPermissionChecker
import com.android.settingslib.ipc.MessengerService
import com.android.settingslib.ipc.PermissionChecker
+import com.android.settingslib.preference.PreferenceScreenProvider
/**
* Preference service providing a bunch of APIs.
@@ -28,14 +32,21 @@ import com.android.settingslib.ipc.PermissionChecker
* [PREFERENCE_SERVICE_ACTION].
*/
open class PreferenceService(
- permissionChecker: PermissionChecker,
name: String = "PreferenceService",
+ permissionChecker: PermissionChecker = PermissionChecker { _, _, _ -> true },
+ preferenceScreenProviders: Set<Class<out PreferenceScreenProvider>> = setOf(),
+ graphPermissionChecker: ApiPermissionChecker<GetPreferenceGraphRequest>? = null,
+ setterPermissionChecker: ApiPermissionChecker<PreferenceSetterRequest>? = null,
+ vararg apiHandlers: ApiHandler<*, *>,
) :
MessengerService(
- listOf<ApiHandler<*, *>>(
- PreferenceGraphApi(),
- PreferenceSetterApiHandler(API_PREFERENCE_SETTER),
- ),
+ mutableListOf<ApiHandler<*, *>>().apply {
+ graphPermissionChecker?.let { add(PreferenceGraphApi(preferenceScreenProviders, it)) }
+ setterPermissionChecker?.let {
+ add(PreferenceSetterApiHandler(API_PREFERENCE_SETTER, it))
+ }
+ addAll(apiHandlers)
+ },
permissionChecker,
name,
)
diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt
index 1f38a6678eae..7655daa6cf21 100644
--- a/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt
+++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt
@@ -18,5 +18,14 @@ package com.android.settingslib.service
const val PREFERENCE_SERVICE_ACTION = "com.android.settingslib.PREFERENCE_SERVICE"
+/** API id for retrieving preference graph. */
internal const val API_GET_PREFERENCE_GRAPH = 1
+
+/** API id for preference value setter. */
internal const val API_PREFERENCE_SETTER = 2
+
+/**
+ * The max API id reserved for internal preference service usages. Custom API id should start with
+ * **1000** to avoid conflict.
+ */
+internal const val API_MAX_RESERVED = 999
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
index 83ee9751329f..80e5e5981912 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
@@ -15,6 +15,7 @@
*/
package com.android.settingslib.media;
+import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
import static android.media.AudioDeviceInfo.TYPE_USB_ACCESSORY;
@@ -103,7 +104,8 @@ public class InputMediaDevice extends MediaDevice {
TYPE_USB_DEVICE,
TYPE_USB_HEADSET,
TYPE_USB_ACCESSORY,
- TYPE_BLUETOOTH_SCO ->
+ TYPE_BLUETOOTH_SCO,
+ TYPE_BLE_HEADSET ->
true;
default -> false;
};
@@ -124,7 +126,7 @@ public class InputMediaDevice extends MediaDevice {
mProductName != null
? mProductName
: mContext.getString(R.string.media_transfer_usb_device_mic_name);
- case TYPE_BLUETOOTH_SCO ->
+ case TYPE_BLUETOOTH_SCO, TYPE_BLE_HEADSET ->
mProductName != null
? mProductName
: mContext.getString(R.string.media_transfer_bt_device_mic_name);
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index 5eeb49a0b398..6842d0a949af 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -204,6 +204,13 @@ public class TestModeBuilder {
return this;
}
+ public TestModeBuilder implicitForPackage(String pkg) {
+ setPackage(pkg);
+ setId(ZenModeConfig.implicitRuleId(pkg));
+ setName("Do Not Disturb (" + pkg + ")");
+ return this;
+ }
+
public TestModeBuilder setActive(boolean active) {
if (active) {
mConfigZenRule.enabled = true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 34d3bd9846d0..d5cfe55813ee 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -208,6 +208,11 @@ public class ZenMode implements Parcelable {
}
@NonNull
+ public Kind getKind() {
+ return mKind;
+ }
+
+ @NonNull
public Status getStatus() {
return mStatus;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
index 7775b912e51d..8624c4df833b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
@@ -38,6 +38,7 @@ public class InputMediaDeviceTest {
private final int WIRED_HEADSET_ID = 2;
private final int USB_HEADSET_ID = 3;
private final int BT_HEADSET_ID = 4;
+ private final int BLE_HEADSET_ID = 5;
private final int MAX_VOLUME = 1;
private final int CURRENT_VOLUME = 0;
private final boolean IS_VOLUME_FIXED = true;
@@ -45,6 +46,7 @@ public class InputMediaDeviceTest {
private static final String PRODUCT_NAME_WIRED_HEADSET = "My Wired Headset";
private static final String PRODUCT_NAME_USB_HEADSET = "My USB Headset";
private static final String PRODUCT_NAME_BT_HEADSET = "My Bluetooth Headset";
+ private static final String PRODUCT_NAME_BLE_HEADSET = "My BLE Headset";
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -163,4 +165,35 @@ public class InputMediaDeviceTest {
assertThat(btMediaDevice.getName())
.isEqualTo(mContext.getString(R.string.media_transfer_bt_device_mic_name));
}
+
+ @Test
+ public void getName_returnCorrectName_bleHeadset() {
+ InputMediaDevice bleMediaDevice =
+ InputMediaDevice.create(
+ mContext,
+ String.valueOf(BLE_HEADSET_ID),
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ MAX_VOLUME,
+ CURRENT_VOLUME,
+ IS_VOLUME_FIXED,
+ PRODUCT_NAME_BLE_HEADSET);
+ assertThat(bleMediaDevice).isNotNull();
+ assertThat(bleMediaDevice.getName()).isEqualTo(PRODUCT_NAME_BLE_HEADSET);
+ }
+
+ @Test
+ public void getName_returnCorrectName_bleHeadset_nullProductName() {
+ InputMediaDevice bleMediaDevice =
+ InputMediaDevice.create(
+ mContext,
+ String.valueOf(BLE_HEADSET_ID),
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ MAX_VOLUME,
+ CURRENT_VOLUME,
+ IS_VOLUME_FIXED,
+ null);
+ assertThat(bleMediaDevice).isNotNull();
+ assertThat(bleMediaDevice.getName())
+ .isEqualTo(mContext.getString(R.string.media_transfer_bt_device_mic_name));
+ }
}
diff --git a/packages/SettingsProvider/res/xml/bookmarks.xml b/packages/SettingsProvider/res/xml/bookmarks.xml
index 22d02262c388..645b275e2af5 100644
--- a/packages/SettingsProvider/res/xml/bookmarks.xml
+++ b/packages/SettingsProvider/res/xml/bookmarks.xml
@@ -32,6 +32,9 @@
'y': YouTube
-->
<bookmarks>
+ <!-- TODO(b/358569822): Remove this from Settings DB
+ This is legacy implementation to store bookmarks in Settings DB, which is deprecated and
+ no longer used -->
<bookmark
role="android.app.role.BROWSER"
shortcut="b" />
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 7b6321d1cc7d..526320debb1a 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -369,6 +369,9 @@
<!-- Permission needed for CTS test - UnsupportedErrorDialogTests -->
<uses-permission android:name="android.permission.RESET_APP_ERRORS" />
+ <!-- Permission needed tests -->
+ <uses-permission android:name="android.permission.OBSERVE_PICTURE_PROFILES" />
+
<!-- Permission needed for CTS test - CtsSystemUiTestCases:PipNotificationTests -->
<uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
@@ -953,6 +956,13 @@
<uses-permission android:name="android.permission.QUERY_ADVANCED_PROTECTION_MODE"
android:featureFlag="android.security.aapm_api"/>
+ <!-- Permission required for CTS test - ForensicManagerTest -->
+ <uses-permission android:name="android.permission.READ_FORENSIC_STATE"
+ android:featureFlag="android.security.afl_api"/>
+ <uses-permission android:name="android.permission.MANAGE_FORENSIC_STATE"
+ android:featureFlag="android.security.afl_api"/>
+
+
<!-- Permission required for CTS test - CtsAppTestCases -->
<uses-permission android:name="android.permission.KILL_UID" />
diff --git a/packages/Shell/aconfig/Android.bp b/packages/Shell/aconfig/Android.bp
new file mode 100644
index 000000000000..1d797b29c1a4
--- /dev/null
+++ b/packages/Shell/aconfig/Android.bp
@@ -0,0 +1,13 @@
+aconfig_declarations {
+ name: "wear_aconfig_declarations",
+ package: "com.android.shell.flags",
+ container: "system",
+ srcs: [
+ "wear.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "wear_aconfig_declarations_flags_java_lib",
+ aconfig_declarations: "wear_aconfig_declarations",
+}
diff --git a/packages/Shell/aconfig/wear.aconfig b/packages/Shell/aconfig/wear.aconfig
new file mode 100644
index 000000000000..e07bd963a4aa
--- /dev/null
+++ b/packages/Shell/aconfig/wear.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.shell.flags"
+container: "system"
+
+flag {
+ name: "handle_bugreports_for_wear"
+ namespace: "wear_services"
+ description: "This flag enables Shell to propagate bugreport results to WearServices."
+ bug: "378060870"
+} \ No newline at end of file
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a4b8821383e0..123f82393679 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1748,6 +1748,13 @@ flag {
}
flag {
+ name: "notification_shade_blur"
+ namespace: "systemui"
+ description: "Enables the new blur effect on the Notification Shade."
+ bug: "370555223"
+}
+
+flag {
name: "ensure_enr_views_visibility"
namespace: "systemui"
description: "Ensures public and private visibilities"
@@ -1790,4 +1797,4 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 18f40c98fe04..eee0cafd34fe 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -24,18 +24,22 @@ import android.app.WindowConfiguration
import android.content.ComponentName
import android.graphics.Color
import android.graphics.Matrix
+import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.os.Binder
import android.os.Build
import android.os.Handler
+import android.os.IBinder
import android.os.Looper
import android.os.RemoteException
+import android.util.ArrayMap
import android.util.Log
import android.view.IRemoteAnimationFinishedCallback
import android.view.IRemoteAnimationRunner
import android.view.RemoteAnimationAdapter
import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
import android.view.SyncRtSurfaceTransactionApplier
import android.view.View
import android.view.ViewGroup
@@ -45,8 +49,12 @@ import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.view.animation.PathInterpolator
+import android.window.IRemoteTransition
+import android.window.IRemoteTransitionFinishedCallback
import android.window.RemoteTransition
import android.window.TransitionFilter
+import android.window.TransitionInfo
+import android.window.WindowAnimationState
import androidx.annotation.AnyThread
import androidx.annotation.BinderThread
import androidx.annotation.UiThread
@@ -55,11 +63,14 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.Flags.activityTransitionUseLargestWindow
import com.android.systemui.Flags.translucentOccludingActivityFix
+import com.android.systemui.animation.TransitionAnimator.Companion.assertLongLivedReturnAnimations
+import com.android.systemui.animation.TransitionAnimator.Companion.assertReturnAnimations
+import com.android.systemui.animation.TransitionAnimator.Companion.longLivedReturnAnimationsEnabled
+import com.android.systemui.animation.TransitionAnimator.Companion.returnAnimationsEnabled
import com.android.systemui.animation.TransitionAnimator.Companion.toTransitionState
-import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
-import com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived
import com.android.wm.shell.shared.IShellTransitions
import com.android.wm.shell.shared.ShellTransitions
+import com.android.wm.shell.shared.TransitionUtil
import java.util.concurrent.Executor
import kotlin.math.roundToInt
@@ -194,7 +205,13 @@ constructor(
private const val LONG_TRANSITION_TIMEOUT = 5_000L
private fun defaultTransitionAnimator(mainExecutor: Executor): TransitionAnimator {
- return TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS)
+ return TransitionAnimator(
+ mainExecutor,
+ TIMINGS,
+ INTERPOLATORS,
+ SPRING_TIMINGS,
+ SPRING_INTERPOLATORS,
+ )
}
private fun defaultDialogToAppAnimator(mainExecutor: Executor): TransitionAnimator {
@@ -275,7 +292,7 @@ constructor(
"ActivityTransitionAnimator.callback must be set before using this animator"
)
val runner = createRunner(controller)
- val runnerDelegate = runner.delegate!!
+ val runnerDelegate = runner.delegate
val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen
// Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the
@@ -330,7 +347,11 @@ constructor(
// If we expect an animation, post a timeout to cancel it in case the remote animation is
// never started.
if (willAnimate) {
- runnerDelegate.postTimeouts()
+ if (longLivedReturnAnimationsEnabled()) {
+ runner.postTimeouts()
+ } else {
+ runnerDelegate!!.postTimeouts()
+ }
// Hide the keyguard using the launch animation instead of the default unlock animation.
if (hideKeyguardWithAnimation) {
@@ -390,7 +411,7 @@ constructor(
launchController: Controller,
transitionRegister: TransitionRegister?,
) {
- if (!returnAnimationFrameworkLibrary()) return
+ if (!returnAnimationsEnabled()) return
var cleanUpRunnable: Runnable? = null
val returnRunner =
@@ -413,7 +434,8 @@ constructor(
private fun cleanUp() {
cleanUpRunnable?.run()
}
- }
+ },
+ initializeLazily = longLivedReturnAnimationsEnabled(),
)
// mTypeSet and mModes match back signals only, and not home. This is on purpose, because
@@ -435,7 +457,11 @@ constructor(
"${launchController.transitionCookie}_returnTransition",
)
- transitionRegister?.register(filter, transition)
+ transitionRegister?.register(
+ filter,
+ transition,
+ includeTakeover = longLivedReturnAnimationsEnabled(),
+ )
cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) }
}
@@ -451,7 +477,10 @@ constructor(
/** Create a new animation [Runner] controlled by [controller]. */
@VisibleForTesting
- fun createRunner(controller: Controller): Runner {
+ @JvmOverloads
+ fun createRunner(controller: Controller, initializeLazily: Boolean = false): Runner {
+ if (initializeLazily) assertLongLivedReturnAnimations()
+
// Make sure we use the modified timings when animating a dialog into an app.
val transitionAnimator =
if (controller.isDialogLaunch) {
@@ -460,7 +489,13 @@ constructor(
transitionAnimator
}
- return Runner(controller, callback!!, transitionAnimator, lifecycleListener)
+ return Runner(
+ controller,
+ callback!!,
+ transitionAnimator,
+ lifecycleListener,
+ initializeLazily,
+ )
}
interface PendingIntentStarter {
@@ -628,10 +663,7 @@ constructor(
* this registration.
*/
fun register(controller: Controller) {
- check(returnAnimationFrameworkLongLived()) {
- "Long-lived registrations cannot be used when the returnAnimationFrameworkLongLived " +
- "flag is disabled"
- }
+ assertLongLivedReturnAnimations()
if (transitionRegister == null) {
throw IllegalStateException(
@@ -667,10 +699,10 @@ constructor(
}
val launchRemoteTransition =
RemoteTransition(
- RemoteAnimationRunnerCompat.wrap(createRunner(controller)),
+ OriginTransition(createRunner(controller, initializeLazily = true)),
"${cookie}_launchTransition",
)
- transitionRegister.register(launchFilter, launchRemoteTransition)
+ transitionRegister.register(launchFilter, launchRemoteTransition, includeTakeover = true)
val returnController =
object : Controller by controller {
@@ -689,10 +721,10 @@ constructor(
}
val returnRemoteTransition =
RemoteTransition(
- RemoteAnimationRunnerCompat.wrap(createRunner(returnController)),
+ OriginTransition(createRunner(returnController, initializeLazily = true)),
"${cookie}_returnTransition",
)
- transitionRegister.register(returnFilter, returnRemoteTransition)
+ transitionRegister.register(returnFilter, returnRemoteTransition, includeTakeover = true)
longLivedTransitions[cookie] = Pair(launchRemoteTransition, returnRemoteTransition)
}
@@ -738,14 +770,154 @@ constructor(
}
}
+ /** [Runner] wrapper that supports animation takeovers. */
+ private inner class OriginTransition(private val runner: Runner) : IRemoteTransition {
+ private val delegate = RemoteAnimationRunnerCompat.wrap(runner)
+
+ init {
+ assertLongLivedReturnAnimations()
+ }
+
+ override fun startAnimation(
+ token: IBinder?,
+ info: TransitionInfo?,
+ t: SurfaceControl.Transaction?,
+ finishCallback: IRemoteTransitionFinishedCallback?,
+ ) {
+ delegate.startAnimation(token, info, t, finishCallback)
+ }
+
+ override fun mergeAnimation(
+ transition: IBinder?,
+ info: TransitionInfo?,
+ t: SurfaceControl.Transaction?,
+ mergeTarget: IBinder?,
+ finishCallback: IRemoteTransitionFinishedCallback?,
+ ) {
+ delegate.mergeAnimation(transition, info, t, mergeTarget, finishCallback)
+ }
+
+ override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {
+ delegate.onTransitionConsumed(transition, aborted)
+ }
+
+ override fun takeOverAnimation(
+ token: IBinder?,
+ info: TransitionInfo?,
+ t: SurfaceControl.Transaction?,
+ finishCallback: IRemoteTransitionFinishedCallback?,
+ states: Array<WindowAnimationState>,
+ ) {
+ if (info == null || t == null) {
+ Log.e(
+ TAG,
+ "Skipping the animation takeover because the required data is missing: " +
+ "info=$info, transaction=$t",
+ )
+ return
+ }
+
+ // The following code converts the contents of the given TransitionInfo into
+ // RemoteAnimationTargets. This is necessary because we must currently support both the
+ // new (Shell, remote transitions) and old (remote animations) framework to maintain
+ // functionality for all users of the library.
+ val apps = ArrayList<RemoteAnimationTarget>()
+ val filteredStates = ArrayList<WindowAnimationState>()
+ val leashMap = ArrayMap<SurfaceControl, SurfaceControl>()
+ val leafTaskFilter = TransitionUtil.LeafTaskFilter()
+
+ // About layering: we divide up the "layer space" into 2 regions (each the size of the
+ // change count). This lets us categorize things into above and below while
+ // maintaining their relative ordering.
+ val belowLayers = info.changes.size
+ val aboveLayers = info.changes.size * 2
+ for (i in info.changes.indices) {
+ val change = info.changes[i]
+ if (change == null || change.taskInfo == null) {
+ continue
+ }
+
+ val taskInfo = change.taskInfo
+
+ if (TransitionUtil.isWallpaper(change)) {
+ val target =
+ TransitionUtil.newTarget(
+ change,
+ belowLayers - i, // wallpapers go into the "below" layer space
+ info,
+ t,
+ leashMap,
+ )
+
+ // Make all the wallpapers opaque.
+ t.setAlpha(target.leash, 1f)
+ } else if (leafTaskFilter.test(change)) {
+ // Start by putting everything into the "below" layer space.
+ val target =
+ TransitionUtil.newTarget(change, belowLayers - i, info, t, leashMap)
+ apps.add(target)
+ filteredStates.add(states[i])
+
+ // Make all the apps opaque.
+ t.setAlpha(target.leash, 1f)
+
+ if (
+ TransitionUtil.isClosingType(change.mode) &&
+ taskInfo?.topActivityType != WindowConfiguration.ACTIVITY_TYPE_HOME
+ ) {
+ // Raise closing task to "above" layer so it isn't covered.
+ t.setLayer(target.leash, aboveLayers - i)
+ }
+ } else if (TransitionInfo.isIndependent(change, info)) {
+ // Root tasks
+ if (TransitionUtil.isClosingType(change.mode)) {
+ // Raise closing task to "above" layer so it isn't covered.
+ t.setLayer(change.leash, aboveLayers - i)
+ } else if (TransitionUtil.isOpeningType(change.mode)) {
+ // Put into the "below" layer space.
+ t.setLayer(change.leash, belowLayers - i)
+ }
+ } else if (TransitionUtil.isDividerBar(change)) {
+ val target =
+ TransitionUtil.newTarget(change, belowLayers - i, info, t, leashMap)
+ apps.add(target)
+ filteredStates.add(states[i])
+ }
+ }
+
+ val wrappedCallback: IRemoteAnimationFinishedCallback =
+ object : IRemoteAnimationFinishedCallback.Stub() {
+ override fun onAnimationFinished() {
+ leashMap.clear()
+ val finishTransaction = SurfaceControl.Transaction()
+ finishCallback?.onTransitionFinished(null, finishTransaction)
+ finishTransaction.close()
+ }
+ }
+
+ runner.takeOverAnimation(
+ apps.toTypedArray(),
+ filteredStates.toTypedArray(),
+ t,
+ wrappedCallback,
+ )
+ }
+
+ override fun asBinder(): IBinder {
+ return delegate.asBinder()
+ }
+ }
+
@VisibleForTesting
inner class Runner(
- controller: Controller,
- callback: Callback,
+ private val controller: Controller,
+ private val callback: Callback,
/** The animator to use to animate the window transition. */
- transitionAnimator: TransitionAnimator,
+ private val transitionAnimator: TransitionAnimator,
/** Listener for animation lifecycle events. */
- listener: Listener? = null,
+ private val listener: Listener? = null,
+ /** Whether the internal [delegate] should be initialized lazily. */
+ private val initializeLazily: Boolean = false,
) : IRemoteAnimationRunner.Stub() {
// This is being passed across IPC boundaries and cycles (through PendingIntentRecords,
// etc.) are possible. So we need to make sure we drop any references that might
@@ -753,15 +925,14 @@ constructor(
@VisibleForTesting var delegate: AnimationDelegate?
init {
- delegate =
- AnimationDelegate(
- mainExecutor,
- controller,
- callback,
- DelegatingAnimationCompletionListener(listener, this::dispose),
- transitionAnimator,
- disableWmTimeout,
- )
+ delegate = null
+ if (!initializeLazily) {
+ // Ephemeral launches bundle the runner with the launch request (instead of being
+ // registered ahead of time for later use). This means that there could be a timeout
+ // between creation and invocation, so the delegate needs to exist from the
+ // beginning in order to handle such timeout.
+ createDelegate()
+ }
}
@BinderThread
@@ -772,6 +943,36 @@ constructor(
nonApps: Array<out RemoteAnimationTarget>?,
finishedCallback: IRemoteAnimationFinishedCallback?,
) {
+ initAndRun(finishedCallback) { delegate ->
+ delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback)
+ }
+ }
+
+ @VisibleForTesting
+ @BinderThread
+ fun takeOverAnimation(
+ apps: Array<RemoteAnimationTarget>?,
+ windowAnimationStates: Array<WindowAnimationState>,
+ startTransaction: SurfaceControl.Transaction,
+ finishedCallback: IRemoteAnimationFinishedCallback?,
+ ) {
+ assertLongLivedReturnAnimations()
+ initAndRun(finishedCallback) { delegate ->
+ delegate.takeOverAnimation(
+ apps,
+ windowAnimationStates,
+ startTransaction,
+ finishedCallback,
+ )
+ }
+ }
+
+ @BinderThread
+ private fun initAndRun(
+ finishedCallback: IRemoteAnimationFinishedCallback?,
+ performAnimation: (AnimationDelegate) -> Unit,
+ ) {
+ maybeSetUp()
val delegate = delegate
mainExecutor.execute {
if (delegate == null) {
@@ -780,7 +981,7 @@ constructor(
// signal back that we're done with it.
finishedCallback?.onAnimationFinished()
} else {
- delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback)
+ performAnimation(delegate)
}
}
}
@@ -794,6 +995,32 @@ constructor(
}
}
+ @VisibleForTesting
+ @UiThread
+ fun postTimeouts() {
+ maybeSetUp()
+ delegate?.postTimeouts()
+ }
+
+ @AnyThread
+ private fun maybeSetUp() {
+ if (!initializeLazily || delegate != null) return
+ createDelegate()
+ }
+
+ @AnyThread
+ private fun createDelegate() {
+ delegate =
+ AnimationDelegate(
+ mainExecutor,
+ controller,
+ callback,
+ DelegatingAnimationCompletionListener(listener, this::dispose),
+ transitionAnimator,
+ disableWmTimeout,
+ )
+ }
+
@AnyThread
fun dispose() {
// Drop references to animation controller once we're done with the animation
@@ -867,7 +1094,7 @@ constructor(
init {
// We do this check here to cover all entry points, including Launcher which doesn't
// call startIntentWithAnimation()
- if (!controller.isLaunching) TransitionAnimator.checkReturnAnimationFrameworkFlag()
+ if (!controller.isLaunching) assertReturnAnimations()
}
@UiThread
@@ -893,19 +1120,73 @@ constructor(
nonApps: Array<out RemoteAnimationTarget>?,
callback: IRemoteAnimationFinishedCallback?,
) {
+ val window = setUpAnimation(apps, callback) ?: return
+
+ if (controller.windowAnimatorState == null || !longLivedReturnAnimationsEnabled()) {
+ val navigationBar =
+ nonApps?.firstOrNull {
+ it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
+ }
+
+ startAnimation(window, navigationBar, iCallback = callback)
+ } else {
+ // If a [controller.windowAnimatorState] exists, treat this like a takeover.
+ takeOverAnimationInternal(
+ window,
+ startWindowStates = null,
+ startTransaction = null,
+ callback,
+ )
+ }
+ }
+
+ @UiThread
+ internal fun takeOverAnimation(
+ apps: Array<out RemoteAnimationTarget>?,
+ startWindowStates: Array<WindowAnimationState>,
+ startTransaction: SurfaceControl.Transaction,
+ callback: IRemoteAnimationFinishedCallback?,
+ ) {
+ val window = setUpAnimation(apps, callback) ?: return
+ takeOverAnimationInternal(window, startWindowStates, startTransaction, callback)
+ }
+
+ private fun takeOverAnimationInternal(
+ window: RemoteAnimationTarget,
+ startWindowStates: Array<WindowAnimationState>?,
+ startTransaction: SurfaceControl.Transaction?,
+ callback: IRemoteAnimationFinishedCallback?,
+ ) {
+ val useSpring =
+ !controller.isLaunching && startWindowStates != null && startTransaction != null
+ startAnimation(
+ window,
+ navigationBar = null,
+ useSpring,
+ startWindowStates,
+ startTransaction,
+ callback,
+ )
+ }
+
+ @UiThread
+ private fun setUpAnimation(
+ apps: Array<out RemoteAnimationTarget>?,
+ callback: IRemoteAnimationFinishedCallback?,
+ ): RemoteAnimationTarget? {
removeTimeouts()
// The animation was started too late and we already notified the controller that it
// timed out.
if (timedOut) {
callback?.invoke()
- return
+ return null
}
// This should not happen, but let's make sure we don't start the animation if it was
// cancelled before and we already notified the controller.
if (cancelled) {
- return
+ return null
}
val window = findTargetWindowIfPossible(apps)
@@ -921,15 +1202,10 @@ constructor(
}
controller.onTransitionAnimationCancelled()
listener?.onTransitionAnimationCancelled()
- return
+ return null
}
- val navigationBar =
- nonApps?.firstOrNull {
- it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
- }
-
- startAnimation(window, navigationBar, callback)
+ return window
}
private fun findTargetWindowIfPossible(
@@ -950,7 +1226,7 @@ constructor(
for (it in apps) {
if (it.mode == targetMode) {
if (activityTransitionUseLargestWindow()) {
- if (returnAnimationFrameworkLibrary()) {
+ if (returnAnimationsEnabled()) {
// If the controller contains a cookie, _only_ match if either the
// candidate contains the matching cookie, or a component is also
// defined and is a match.
@@ -995,8 +1271,11 @@ constructor(
private fun startAnimation(
window: RemoteAnimationTarget,
- navigationBar: RemoteAnimationTarget?,
- iCallback: IRemoteAnimationFinishedCallback?,
+ navigationBar: RemoteAnimationTarget? = null,
+ useSpring: Boolean = false,
+ startingWindowStates: Array<WindowAnimationState>? = null,
+ startTransaction: SurfaceControl.Transaction? = null,
+ iCallback: IRemoteAnimationFinishedCallback? = null,
) {
if (TransitionAnimator.DEBUG) {
Log.d(TAG, "Remote animation started")
@@ -1046,18 +1325,66 @@ constructor(
val controller =
object : Controller by delegate {
override fun createAnimatorState(): TransitionAnimator.State {
- if (isLaunching) return delegate.createAnimatorState()
- return delegate.windowAnimatorState?.toTransitionState()
- ?: getWindowRadius(isExpandingFullyAbove).let {
- TransitionAnimator.State(
- top = windowBounds.top,
- bottom = windowBounds.bottom,
- left = windowBounds.left,
- right = windowBounds.right,
- topCornerRadius = it,
- bottomCornerRadius = it,
- )
+ if (isLaunching) {
+ return delegate.createAnimatorState()
+ } else if (!longLivedReturnAnimationsEnabled()) {
+ return delegate.windowAnimatorState?.toTransitionState()
+ ?: getWindowRadius(isExpandingFullyAbove).let {
+ TransitionAnimator.State(
+ top = windowBounds.top,
+ bottom = windowBounds.bottom,
+ left = windowBounds.left,
+ right = windowBounds.right,
+ topCornerRadius = it,
+ bottomCornerRadius = it,
+ )
+ }
+ }
+
+ // The states are sorted matching the changes inside the transition info.
+ // Using this info, the RemoteAnimationTargets are created, with their
+ // prefixOrderIndex fields in reverse order to that of changes. To extract
+ // the right state, we need to invert again.
+ val windowState =
+ if (startingWindowStates != null) {
+ startingWindowStates[
+ startingWindowStates.size - window.prefixOrderIndex]
+ } else {
+ controller.windowAnimatorState
}
+
+ // TODO(b/323863002): use the timestamp and velocity to update the initial
+ // position.
+ val bounds = windowState?.bounds
+ val left: Int = bounds?.left?.toInt() ?: windowBounds.left
+ val top: Int = bounds?.top?.toInt() ?: windowBounds.top
+ val right: Int = bounds?.right?.toInt() ?: windowBounds.right
+ val bottom: Int = bounds?.bottom?.toInt() ?: windowBounds.bottom
+
+ val width = windowBounds.right - windowBounds.left
+ val height = windowBounds.bottom - windowBounds.top
+ // Scale the window. We use the max of (widthRatio, heightRatio) so that
+ // there is no blank space on any side.
+ val widthRatio = (right - left).toFloat() / width
+ val heightRatio = (bottom - top).toFloat() / height
+ val startScale = maxOf(widthRatio, heightRatio)
+
+ val maybeRadius = windowState?.topLeftRadius
+ val windowRadius =
+ if (maybeRadius != null) {
+ maybeRadius * startScale
+ } else {
+ getWindowRadius(isExpandingFullyAbove)
+ }
+
+ return TransitionAnimator.State(
+ top = top,
+ bottom = bottom,
+ left = left,
+ right = right,
+ topCornerRadius = windowRadius,
+ bottomCornerRadius = windowRadius,
+ )
}
override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -1071,6 +1398,19 @@ constructor(
"[controller=$delegate]",
)
}
+
+ if (startTransaction != null) {
+ // Calling applyStateToWindow() here avoids skipping a frame when taking
+ // over an animation.
+ applyStateToWindow(
+ window,
+ createAnimatorState(),
+ linearProgress = 0f,
+ useSpring,
+ startTransaction,
+ )
+ }
+
delegate.onTransitionAnimationStart(isExpandingFullyAbove)
}
@@ -1094,14 +1434,29 @@ constructor(
progress: Float,
linearProgress: Float,
) {
- applyStateToWindow(window, state, linearProgress)
+ applyStateToWindow(window, state, linearProgress, useSpring)
navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
listener?.onTransitionAnimationProgress(linearProgress)
delegate.onTransitionAnimationProgress(state, progress, linearProgress)
}
}
-
+ val windowState =
+ if (startingWindowStates != null) {
+ startingWindowStates[startingWindowStates.size - window.prefixOrderIndex]
+ } else {
+ controller.windowAnimatorState
+ }
+ val velocityPxPerS =
+ if (longLivedReturnAnimationsEnabled() && windowState?.velocityPxPerMs != null) {
+ val xVelocityPxPerS = windowState.velocityPxPerMs.x * 1000
+ val yVelocityPxPerS = windowState.velocityPxPerMs.y * 1000
+ PointF(xVelocityPxPerS, yVelocityPxPerS)
+ } else if (useSpring) {
+ PointF(0f, 0f)
+ } else {
+ null
+ }
animation =
transitionAnimator.startAnimation(
controller,
@@ -1109,6 +1464,7 @@ constructor(
windowBackgroundColor,
fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow,
drawHole = !controller.isBelowAnimatingWindow,
+ startVelocity = velocityPxPerS,
)
}
@@ -1128,6 +1484,8 @@ constructor(
window: RemoteAnimationTarget,
state: TransitionAnimator.State,
linearProgress: Float,
+ useSpring: Boolean,
+ transaction: SurfaceControl.Transaction? = null,
) {
if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) {
// Don't apply any transaction if the view root we synchronize with was detached or
@@ -1171,25 +1529,47 @@ constructor(
windowCropF.bottom.roundToInt(),
)
- val windowAnimationDelay =
+ val interpolators: TransitionAnimator.Interpolators
+ val windowProgress: Float
+
+ if (useSpring) {
+ val windowAnimationDelay: Float
+ val windowAnimationDuration: Float
if (controller.isLaunching) {
- TIMINGS.contentAfterFadeInDelay
+ windowAnimationDelay = SPRING_TIMINGS.contentAfterFadeInDelay
+ windowAnimationDuration = SPRING_TIMINGS.contentAfterFadeInDuration
} else {
- TIMINGS.contentBeforeFadeOutDelay
+ windowAnimationDelay = SPRING_TIMINGS.contentBeforeFadeOutDelay
+ windowAnimationDuration = SPRING_TIMINGS.contentBeforeFadeOutDuration
}
- val windowAnimationDuration =
+
+ interpolators = SPRING_INTERPOLATORS
+ windowProgress =
+ TransitionAnimator.getProgress(
+ linearProgress,
+ windowAnimationDelay,
+ windowAnimationDuration,
+ )
+ } else {
+ val windowAnimationDelay: Long
+ val windowAnimationDuration: Long
if (controller.isLaunching) {
- TIMINGS.contentAfterFadeInDuration
+ windowAnimationDelay = TIMINGS.contentAfterFadeInDelay
+ windowAnimationDuration = TIMINGS.contentAfterFadeInDuration
} else {
- TIMINGS.contentBeforeFadeOutDuration
+ windowAnimationDelay = TIMINGS.contentBeforeFadeOutDelay
+ windowAnimationDuration = TIMINGS.contentBeforeFadeOutDuration
}
- val windowProgress =
- TransitionAnimator.getProgress(
- TIMINGS,
- linearProgress,
- windowAnimationDelay,
- windowAnimationDuration,
- )
+
+ interpolators = INTERPOLATORS
+ windowProgress =
+ TransitionAnimator.getProgress(
+ TIMINGS,
+ linearProgress,
+ windowAnimationDelay,
+ windowAnimationDuration,
+ )
+ }
// The alpha of the opening window. If it opens above the expandable, then it should
// fade in progressively. Otherwise, it should be fully opaque and will be progressively
@@ -1197,12 +1577,12 @@ constructor(
val alpha =
if (controller.isBelowAnimatingWindow) {
if (controller.isLaunching) {
- INTERPOLATORS.contentAfterFadeInInterpolator.getInterpolation(
+ interpolators.contentAfterFadeInInterpolator.getInterpolation(
windowProgress
)
} else {
1 -
- INTERPOLATORS.contentBeforeFadeOutInterpolator.getInterpolation(
+ interpolators.contentBeforeFadeOutInterpolator.getInterpolation(
windowProgress
)
}
@@ -1216,6 +1596,7 @@ constructor(
// especially important for lock screen animations, where the window is not clipped by
// the shade.
val cornerRadius = maxOf(state.topCornerRadius, state.bottomCornerRadius) / scale
+
val params =
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash)
.withAlpha(alpha)
@@ -1223,11 +1604,15 @@ constructor(
.withWindowCrop(windowCrop)
.withCornerRadius(cornerRadius)
.withVisibility(true)
- .build()
+ if (transaction != null) params.withMergeTransaction(transaction)
- transactionApplier.scheduleApply(params)
+ transactionApplier.scheduleApply(params.build())
}
+ // TODO(b/377643129): remote transitions have no way of identifying the navbar when
+ // converting to RemoteAnimationTargets (and in my testing it was never included in the
+ // transition at all). So this method is not used anymore. Remove or adapt once we fully
+ // convert to remote transitions.
private fun applyStateToNavigationBar(
navigationBar: RemoteAnimationTarget,
state: TransitionAnimator.State,
@@ -1362,9 +1747,17 @@ constructor(
}
/** Register [remoteTransition] with WM Shell using the given [filter]. */
- internal fun register(filter: TransitionFilter, remoteTransition: RemoteTransition) {
+ internal fun register(
+ filter: TransitionFilter,
+ remoteTransition: RemoteTransition,
+ includeTakeover: Boolean,
+ ) {
shellTransitions?.registerRemote(filter, remoteTransition)
iShellTransitions?.registerRemote(filter, remoteTransition)
+ if (includeTakeover) {
+ shellTransitions?.registerRemoteForTakeover(filter, remoteTransition)
+ iShellTransitions?.registerRemoteForTakeover(filter, remoteTransition)
+ }
}
/** Unregister [remoteTransition] from WM Shell. */
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index fdb4871423c3..de4bdbc284c4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -38,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.internal.dynamicanimation.animation.SpringAnimation
import com.android.internal.dynamicanimation.animation.SpringForce
import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
+import com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived
import java.util.concurrent.Executor
import kotlin.math.abs
import kotlin.math.max
@@ -113,13 +114,26 @@ class TransitionAnimator(
)
}
- internal fun checkReturnAnimationFrameworkFlag() {
- check(returnAnimationFrameworkLibrary()) {
- "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag is " +
- "disabled"
+ internal fun assertReturnAnimations() {
+ check(returnAnimationsEnabled()) {
+ "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag " +
+ "is disabled"
}
}
+ internal fun returnAnimationsEnabled() = returnAnimationFrameworkLibrary()
+
+ internal fun assertLongLivedReturnAnimations() {
+ check(longLivedReturnAnimationsEnabled()) {
+ "Long-lived registrations cannot be used when the " +
+ "returnAnimationFrameworkLibrary or the " +
+ "returnAnimationFrameworkLongLived flag are disabled"
+ }
+ }
+
+ internal fun longLivedReturnAnimationsEnabled() =
+ returnAnimationFrameworkLibrary() && returnAnimationFrameworkLongLived()
+
internal fun WindowAnimationState.toTransitionState() =
State().also {
bounds?.let { b ->
@@ -467,7 +481,8 @@ class TransitionAnimator(
drawHole: Boolean = false,
startVelocity: PointF? = null,
): Animation {
- if (!controller.isLaunching || startVelocity != null) checkReturnAnimationFrameworkFlag()
+ if (!controller.isLaunching) assertReturnAnimations()
+ if (startVelocity != null) assertLongLivedReturnAnimations()
// We add an extra layer with the same color as the dialog/app splash screen background
// color, which is usually the same color of the app background. We first fade in this layer
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 96e99b15363d..778d7e7f99b3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -67,6 +67,7 @@ constructor(
dialogFactory = dialogFactory,
widgetSection = widgetSection,
modifier = Modifier.element(Communal.Elements.Grid),
+ sceneScope = this@Content,
)
}
with(lockSection) {
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 df1185cb1a6d..5b1203fa17c9 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
@@ -163,6 +163,7 @@ import androidx.compose.ui.zIndex
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.layout.WindowMetricsCalculator
import com.android.compose.animation.Easings.Emphasized
+import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.internal.R.dimen.system_app_widget_background_radius
@@ -186,7 +187,9 @@ import com.android.systemui.communal.util.DensityUtils.Companion.adjustedDp
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import kotlin.math.max
import kotlin.math.min
@@ -205,6 +208,7 @@ fun CommunalHub(
widgetConfigurator: WidgetConfigurator? = null,
onOpenWidgetPicker: (() -> Unit)? = null,
onEditDone: (() -> Unit)? = null,
+ sceneScope: SceneScope? = null,
) {
val communalContent by
viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList())
@@ -414,6 +418,7 @@ fun CommunalHub(
widgetConfigurator = widgetConfigurator,
interactionHandler = interactionHandler,
widgetSection = widgetSection,
+ sceneScope = sceneScope,
)
}
}
@@ -735,6 +740,7 @@ private fun BoxScope.CommunalHubLazyGrid(
widgetConfigurator: WidgetConfigurator?,
interactionHandler: RemoteViews.InteractionHandler?,
widgetSection: CommunalAppWidgetSection,
+ sceneScope: SceneScope?,
) {
var gridModifier =
Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
@@ -871,6 +877,7 @@ private fun BoxScope.CommunalHubLazyGrid(
interactionHandler = interactionHandler,
widgetSection = widgetSection,
resizeableItemFrameViewModel = resizeableItemFrameViewModel,
+ sceneScope = sceneScope,
)
}
}
@@ -1095,6 +1102,7 @@ private fun CommunalContent(
interactionHandler: RemoteViews.InteractionHandler?,
widgetSection: CommunalAppWidgetSection,
resizeableItemFrameViewModel: ResizeableItemFrameViewModel,
+ sceneScope: SceneScope? = null,
) {
when (model) {
is CommunalContentModel.WidgetContent.Widget ->
@@ -1118,7 +1126,7 @@ private fun CommunalContent(
is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier)
is CommunalContentModel.Smartspace -> SmartspaceContent(interactionHandler, model, modifier)
is CommunalContentModel.Tutorial -> TutorialContent(modifier)
- is CommunalContentModel.Umo -> Umo(viewModel, modifier)
+ is CommunalContentModel.Umo -> Umo(viewModel, sceneScope, modifier)
}
}
@@ -1529,7 +1537,25 @@ private fun TutorialContent(modifier: Modifier = Modifier) {
}
@Composable
-private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) {
+private fun Umo(
+ viewModel: BaseCommunalViewModel,
+ sceneScope: SceneScope?,
+ modifier: Modifier = Modifier,
+) {
+ if (SceneContainerFlag.isEnabled && sceneScope != null) {
+ sceneScope.MediaCarousel(
+ modifier = modifier.fillMaxSize(),
+ isVisible = true,
+ mediaHost = viewModel.mediaHost,
+ carouselController = viewModel.mediaCarouselController,
+ )
+ } else {
+ UmoLegacy(viewModel, modifier)
+ }
+}
+
+@Composable
+private fun UmoLegacy(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) {
AndroidView(
modifier =
modifier.pointerInput(Unit) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index d546a5db495e..9de7a5d659ae 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -16,6 +16,7 @@ import com.android.systemui.scene.ui.composable.transitions.bouncerToLockscreenP
import com.android.systemui.scene.ui.composable.transitions.communalToBouncerTransition
import com.android.systemui.scene.ui.composable.transitions.communalToShadeTransition
import com.android.systemui.scene.ui.composable.transitions.dreamToBouncerTransition
+import com.android.systemui.scene.ui.composable.transitions.dreamToCommunalTransition
import com.android.systemui.scene.ui.composable.transitions.dreamToGoneTransition
import com.android.systemui.scene.ui.composable.transitions.dreamToShadeTransition
import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
@@ -58,6 +59,7 @@ val SceneContainerTransitions = transitions {
from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() }
from(Scenes.Dream, to = Scenes.Bouncer) { dreamToBouncerTransition() }
+ from(Scenes.Dream, to = Scenes.Communal) { dreamToCommunalTransition() }
from(Scenes.Dream, to = Scenes.Gone) { dreamToGoneTransition() }
from(Scenes.Dream, to = Scenes.Shade) { dreamToShadeTransition() }
from(Scenes.Gone, to = Scenes.Shade) { goneToShadeTransition() }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToCommunalTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToCommunalTransition.kt
new file mode 100644
index 000000000000..93c10b6224ab
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToCommunalTransition.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.communal.ui.compose.AllElements
+import com.android.systemui.communal.ui.compose.Communal
+
+fun TransitionBuilder.dreamToCommunalTransition() {
+ spec = tween(durationMillis = 1000)
+
+ // Translate communal hub grid from the end direction.
+ translate(Communal.Elements.Grid, Edge.End)
+
+ // Fade all communal hub elements.
+ timestampRange(startMillis = 167, endMillis = 334) { fade(AllElements) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
index 71fa6c9e567a..ce7a85b19fb4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
@@ -19,11 +19,8 @@ package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
-import com.android.compose.animation.scene.UserActionDistanceScope
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
@@ -31,24 +28,17 @@ import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
import kotlin.time.Duration.Companion.milliseconds
-fun TransitionBuilder.goneToSplitShadeTransition(
- durationScale: Double = 1.0,
-) {
+fun TransitionBuilder.goneToSplitShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
swipeSpec =
spring(
stiffness = Spring.StiffnessMediumLow,
visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
)
- distance =
- object : UserActionDistance {
- override fun UserActionDistanceScope.absoluteDistance(
- fromSceneSize: IntSize,
- orientation: Orientation,
- ): Float {
- return fromSceneSize.height.toFloat() * 2 / 3f
- }
- }
+ distance = UserActionDistance { fromContent, _, _ ->
+ val fromContentSize = checkNotNull(fromContent.targetSize())
+ fromContentSize.height.toFloat() * 2 / 3f
+ }
fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
index 1486ea7d53b5..1f7a7380bbc6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
@@ -19,35 +19,25 @@ package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
-import com.android.compose.animation.scene.UserActionDistanceScope
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
import kotlin.time.Duration.Companion.milliseconds
-fun TransitionBuilder.lockscreenToSplitShadeTransition(
- durationScale: Double = 1.0,
-) {
+fun TransitionBuilder.lockscreenToSplitShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
swipeSpec =
spring(
stiffness = Spring.StiffnessMediumLow,
visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
)
- distance =
- object : UserActionDistance {
- override fun UserActionDistanceScope.absoluteDistance(
- fromSceneSize: IntSize,
- orientation: Orientation,
- ): Float {
- return fromSceneSize.height.toFloat() * 2 / 3f
- }
- }
+ distance = UserActionDistance { fromContent, _, _ ->
+ val fromContentSize = checkNotNull(fromContent.targetSize())
+ fromContentSize.height.toFloat() * 2 / 3f
+ }
fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
index 8cd357ef92a5..ba1972fbcc5a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -1,46 +1,36 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
-import com.android.compose.animation.scene.UserActionDistanceScope
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.ui.composable.ShadeHeader
import kotlin.time.Duration.Companion.milliseconds
-fun TransitionBuilder.shadeToQuickSettingsTransition(
- durationScale: Double = 1.0,
-) {
+fun TransitionBuilder.shadeToQuickSettingsTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- distance =
- object : UserActionDistance {
- override fun UserActionDistanceScope.absoluteDistance(
- fromSceneSize: IntSize,
- orientation: Orientation,
- ): Float {
- val distance =
- Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y
- ?: return 0f
- return fromSceneSize.height - distance
- }
- }
+ distance = UserActionDistance { fromContent, _, _ ->
+ val distance =
+ Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y
+ ?: return@UserActionDistance 0f
+ val fromContentSize = checkNotNull(fromContent.targetSize())
+ fromContentSize.height - distance
+ }
translate(Notifications.Elements.NotificationScrim, Edge.Bottom)
timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
translate(
ShadeHeader.Elements.CollapsedContentStart,
- y = ShadeHeader.Dimensions.CollapsedHeight
+ y = ShadeHeader.Dimensions.CollapsedHeight,
)
translate(ShadeHeader.Elements.CollapsedContentEnd, y = ShadeHeader.Dimensions.CollapsedHeight)
translate(
ShadeHeader.Elements.ExpandedContent,
- y = -(ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight)
+ y = -(ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight),
)
translate(ShadeHeader.Elements.ShadeCarrierGroup, y = -ShadeHeader.Dimensions.CollapsedHeight)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt
index de76f708c1c7..d35537a74c85 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt
@@ -28,8 +28,9 @@ private const val TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f
fun TransitionBuilder.toBouncerTransition() {
spec = tween(durationMillis = 500)
- distance = UserActionDistance { fromSceneSize, _ ->
- fromSceneSize.height * TO_BOUNCER_SWIPE_DISTANCE_FRACTION
+ distance = UserActionDistance { fromContent, _, _ ->
+ val fromContentSize = checkNotNull(fromContent.targetSize())
+ fromContentSize.height * TO_BOUNCER_SWIPE_DISTANCE_FRACTION
}
translate(Bouncer.Elements.Content, y = 300.dp)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
index 55fa6ad94ed3..e78bc6afcc4f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -33,7 +33,10 @@ fun TransitionBuilder.toQuickSettingsShadeTransition(durationScale: Double = 1.0
stiffness = Spring.StiffnessMediumLow,
visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
)
- distance = UserActionDistance { fromSceneSize, _ -> fromSceneSize.height.toFloat() * 2 / 3f }
+ distance = UserActionDistance { fromContent, _, _ ->
+ val fromContentSize = checkNotNull(fromContent.targetSize())
+ fromContentSize.height.toFloat() * 2 / 3f
+ }
translate(OverlayShade.Elements.Panel, Edge.Top)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
index b677dff2dcf9..bfae4897dc68 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
@@ -19,12 +19,9 @@ package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
-import com.android.compose.animation.scene.UserActionDistanceScope
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
@@ -33,24 +30,16 @@ import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
import kotlin.time.Duration.Companion.milliseconds
-fun TransitionBuilder.toShadeTransition(
- durationScale: Double = 1.0,
-) {
+fun TransitionBuilder.toShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
swipeSpec =
spring(
stiffness = Spring.StiffnessMediumLow,
visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
)
- distance =
- object : UserActionDistance {
- override fun UserActionDistanceScope.absoluteDistance(
- fromSceneSize: IntSize,
- orientation: Orientation,
- ): Float {
- return Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y ?: 0f
- }
- }
+ distance = UserActionDistance { _, _, _ ->
+ Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y ?: 0f
+ }
fractionRange(start = .58f) {
fade(ShadeHeader.Elements.Clock)
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 dbf7d7b29834..d3ddb5003469 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
@@ -637,8 +637,8 @@ sealed class UserActionResult(
fun interface UserActionDistance {
/**
- * Return the **absolute** distance of the user action given the size of the scene we are
- * animating from and the [orientation].
+ * Return the **absolute** distance of the user action when going from [fromContent] to
+ * [toContent] in the given [orientation].
*
* Note: This function will be called for each drag event until it returns a value > 0f. This
* for instance allows you to return 0f or a negative value until the first layout pass of a
@@ -646,7 +646,8 @@ fun interface UserActionDistance {
* transitioning to when computing this absolute distance.
*/
fun UserActionDistanceScope.absoluteDistance(
- fromSceneSize: IntSize,
+ fromContent: ContentKey,
+ toContent: ContentKey,
orientation: Orientation,
): Float
}
@@ -656,7 +657,8 @@ interface UserActionDistanceScope : Density, ElementStateScope
/** The user action has a fixed [absoluteDistance]. */
class FixedDistance(private val distance: Dp) : UserActionDistance {
override fun UserActionDistanceScope.absoluteDistance(
- fromSceneSize: IntSize,
+ fromContent: ContentKey,
+ toContent: ContentKey,
orientation: Orientation,
): Float = distance.toPx()
}
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 7eb5a3f8b362..3bf2ed50b709 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
@@ -267,11 +267,12 @@ internal class MutableSceneTransitionLayoutStateImpl(
private set
/**
- * The flattened list of [SharedElementTransformation] within all the transitions in
+ * The flattened list of [SharedElementTransformation.Factory] within all the transitions in
* [transitionStates].
*/
- private val transformationsWithElevation: List<SharedElementTransformation> by derivedStateOf {
- transformationsWithElevation(transitionStates)
+ private val transformationFactoriesWithElevation:
+ List<SharedElementTransformation.Factory> by derivedStateOf {
+ transformationFactoriesWithElevation(transitionStates)
}
override val currentScene: SceneKey
@@ -355,6 +356,12 @@ internal class MutableSceneTransitionLayoutStateImpl(
override suspend fun startTransition(transition: TransitionState.Transition, chain: Boolean) {
checkThread()
+ // Prepare the transition before starting it. This is outside of the try/finally block on
+ // purpose because preparing a transition might throw an exception (e.g. if we find multiple
+ // specs matching this transition), in which case we want to throw that exception here
+ // before even starting the transition.
+ prepareTransitionBeforeStarting(transition)
+
try {
// Start the transition.
startTransitionInternal(transition, chain)
@@ -366,7 +373,7 @@ internal class MutableSceneTransitionLayoutStateImpl(
}
}
- private fun startTransitionInternal(transition: TransitionState.Transition, chain: Boolean) {
+ private fun prepareTransitionBeforeStarting(transition: TransitionState.Transition) {
// Set the current scene and overlays on the transition.
val currentState = transitionState
transition.currentSceneWhenTransitionStarted = currentState.currentScene
@@ -394,7 +401,9 @@ internal class MutableSceneTransitionLayoutStateImpl(
} else {
transition.updateOverscrollSpecs(fromSpec = null, toSpec = null)
}
+ }
+ private fun startTransitionInternal(transition: TransitionState.Transition, chain: Boolean) {
when (val currentState = transitionStates.last()) {
is TransitionState.Idle -> {
// Replace [Idle] by [transition].
@@ -692,22 +701,23 @@ internal class MutableSceneTransitionLayoutStateImpl(
animate()
}
- private fun transformationsWithElevation(
+ private fun transformationFactoriesWithElevation(
transitionStates: List<TransitionState>
- ): List<SharedElementTransformation> {
+ ): List<SharedElementTransformation.Factory> {
return buildList {
transitionStates.fastForEach { state ->
if (state !is TransitionState.Transition) {
return@fastForEach
}
- state.transformationSpec.transformations.fastForEach { transformationWithRange ->
- val transformation = transformationWithRange.transformation
+ state.transformationSpec.transformationMatchers.fastForEach { transformationMatcher
+ ->
+ val factory = transformationMatcher.factory
if (
- transformation is SharedElementTransformation &&
- transformation.elevateInContent != null
+ factory is SharedElementTransformation.Factory &&
+ factory.elevateInContent != null
) {
- add(transformation)
+ add(factory)
}
}
}
@@ -722,10 +732,10 @@ internal class MutableSceneTransitionLayoutStateImpl(
* necessary, for performance.
*/
internal fun isElevationPossible(content: ContentKey, element: ElementKey?): Boolean {
- if (transformationsWithElevation.isEmpty()) return false
- return transformationsWithElevation.fastAny { transformation ->
- transformation.elevateInContent == content &&
- (element == null || transformation.matcher.matches(element, content))
+ if (transformationFactoriesWithElevation.isEmpty()) return false
+ return transformationFactoriesWithElevation.fastAny { factory ->
+ factory.elevateInContent == content &&
+ (element == null || factory.matcher.matches(element, content))
}
}
}
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 569593c3eb59..8df3f2d932b3 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
@@ -26,17 +26,9 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
import com.android.compose.animation.scene.content.state.TransitionState
-import com.android.compose.animation.scene.transformation.CustomAlphaTransformation
-import com.android.compose.animation.scene.transformation.CustomOffsetTransformation
-import com.android.compose.animation.scene.transformation.CustomScaleTransformation
-import com.android.compose.animation.scene.transformation.CustomSizeTransformation
-import com.android.compose.animation.scene.transformation.InterpolatedAlphaTransformation
-import com.android.compose.animation.scene.transformation.InterpolatedOffsetTransformation
-import com.android.compose.animation.scene.transformation.InterpolatedScaleTransformation
-import com.android.compose.animation.scene.transformation.InterpolatedSizeTransformation
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.SharedElementTransformation
-import com.android.compose.animation.scene.transformation.Transformation
+import com.android.compose.animation.scene.transformation.TransformationMatcher
import com.android.compose.animation.scene.transformation.TransformationWithRange
/** The transitions configuration of a [SceneTransitionLayout]. */
@@ -169,7 +161,7 @@ internal constructor(
}
/** The definition of a transition between [from] and [to]. */
-interface TransitionSpec {
+internal interface TransitionSpec {
/** The key of this [TransitionSpec]. */
val key: TransitionKey?
@@ -209,7 +201,7 @@ interface TransitionSpec {
fun previewTransformationSpec(transition: TransitionState.Transition): TransformationSpec?
}
-interface TransformationSpec {
+internal interface TransformationSpec {
/**
* The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
* the transition is triggered (i.e. it is not gesture-based).
@@ -232,8 +224,8 @@ interface TransformationSpec {
*/
val distance: UserActionDistance?
- /** The list of [Transformation] applied to elements during this transition. */
- val transformations: List<TransformationWithRange<*>>
+ /** The list of [TransformationMatcher] applied to elements during this transformation. */
+ val transformationMatchers: List<TransformationMatcher>
companion object {
internal val Empty =
@@ -241,7 +233,7 @@ interface TransformationSpec {
progressSpec = snap(),
swipeSpec = null,
distance = null,
- transformations = emptyList(),
+ transformationMatchers = emptyList(),
)
internal val EmptyProvider = { _: TransitionState.Transition -> Empty }
}
@@ -272,7 +264,14 @@ internal class TransitionSpecImpl(
progressSpec = reverse.progressSpec,
swipeSpec = reverse.swipeSpec,
distance = reverse.distance,
- transformations = reverse.transformations.map { it.reversed() },
+ transformationMatchers =
+ reverse.transformationMatchers.map {
+ TransformationMatcher(
+ matcher = it.matcher,
+ factory = it.factory,
+ range = it.range?.reversed(),
+ )
+ },
)
},
)
@@ -288,7 +287,7 @@ internal class TransitionSpecImpl(
}
/** The definition of the overscroll behavior of the [content]. */
-interface OverscrollSpec {
+internal interface OverscrollSpec {
/** The scene we are over scrolling. */
val content: ContentKey
@@ -325,7 +324,7 @@ internal class TransformationSpecImpl(
override val progressSpec: AnimationSpec<Float>,
override val swipeSpec: SpringSpec<Float>?,
override val distance: UserActionDistance?,
- override val transformations: List<TransformationWithRange<*>>,
+ override val transformationMatchers: List<TransformationMatcher>,
) : TransformationSpec {
private val cache = mutableMapOf<ElementKey, MutableMap<ContentKey, ElementTransformations>>()
@@ -335,7 +334,7 @@ internal class TransformationSpecImpl(
.getOrPut(content) { computeTransformations(element, content) }
}
- /** Filter [transformations] to compute the [ElementTransformations] of [element]. */
+ /** Filter [transformationMatchers] to compute the [ElementTransformations] of [element]. */
private fun computeTransformations(
element: ElementKey,
content: ContentKey,
@@ -346,46 +345,55 @@ internal class TransformationSpecImpl(
var drawScale: TransformationWithRange<PropertyTransformation<Scale>>? = null
var alpha: TransformationWithRange<PropertyTransformation<Float>>? = null
- transformations.fastForEach { transformationWithRange ->
- val transformation = transformationWithRange.transformation
- if (!transformation.matcher.matches(element, content)) {
+ transformationMatchers.fastForEach { transformationMatcher ->
+ if (!transformationMatcher.matcher.matches(element, content)) {
return@fastForEach
}
- when (transformation) {
- is SharedElementTransformation -> {
- throwIfNotNull(shared, element, name = "shared")
- shared =
- transformationWithRange
- as TransformationWithRange<SharedElementTransformation>
+ val transformation = transformationMatcher.factory.create()
+ val property =
+ when (transformation) {
+ is SharedElementTransformation -> {
+ throwIfNotNull(shared, element, name = "shared")
+ shared =
+ TransformationWithRange(transformation, transformationMatcher.range)
+ return@fastForEach
+ }
+ is PropertyTransformation<*> -> transformation.property
}
- is InterpolatedOffsetTransformation,
- is CustomOffsetTransformation -> {
+
+ when (property) {
+ is PropertyTransformation.Property.Offset -> {
throwIfNotNull(offset, element, name = "offset")
offset =
- transformationWithRange
- as TransformationWithRange<PropertyTransformation<Offset>>
+ TransformationWithRange(
+ transformation as PropertyTransformation<Offset>,
+ transformationMatcher.range,
+ )
}
- is InterpolatedSizeTransformation,
- is CustomSizeTransformation -> {
+ is PropertyTransformation.Property.Size -> {
throwIfNotNull(size, element, name = "size")
size =
- transformationWithRange
- as TransformationWithRange<PropertyTransformation<IntSize>>
+ TransformationWithRange(
+ transformation as PropertyTransformation<IntSize>,
+ transformationMatcher.range,
+ )
}
- is InterpolatedScaleTransformation,
- is CustomScaleTransformation -> {
+ is PropertyTransformation.Property.Scale -> {
throwIfNotNull(drawScale, element, name = "drawScale")
drawScale =
- transformationWithRange
- as TransformationWithRange<PropertyTransformation<Scale>>
+ TransformationWithRange(
+ transformation as PropertyTransformation<Scale>,
+ transformationMatcher.range,
+ )
}
- is InterpolatedAlphaTransformation,
- is CustomAlphaTransformation -> {
+ is PropertyTransformation.Property.Alpha -> {
throwIfNotNull(alpha, element, name = "alpha")
alpha =
- transformationWithRange
- as TransformationWithRange<PropertyTransformation<Float>>
+ TransformationWithRange(
+ transformation as PropertyTransformation<Float>,
+ transformationMatcher.range,
+ )
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index f0043e1e89b0..dbfeb5cd0168 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -24,7 +24,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
-import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
import kotlin.math.absoluteValue
@@ -66,8 +65,9 @@ internal fun createSwipeAnimation(
val absoluteDistance =
with(animation.contentTransition.transformationSpec.distance ?: DefaultSwipeDistance) {
layoutImpl.userActionDistanceScope.absoluteDistance(
- layoutImpl.content(animation.fromContent).targetSize,
- orientation,
+ fromContent = animation.fromContent,
+ toContent = animation.toContent,
+ orientation = orientation,
)
}
@@ -475,12 +475,14 @@ internal class SwipeAnimation<T : ContentKey>(
private object DefaultSwipeDistance : UserActionDistance {
override fun UserActionDistanceScope.absoluteDistance(
- fromSceneSize: IntSize,
+ fromContent: ContentKey,
+ toContent: ContentKey,
orientation: Orientation,
): Float {
+ val fromContentSize = checkNotNull(fromContent.targetSize())
return when (orientation) {
- Orientation.Horizontal -> fromSceneSize.width
- Orientation.Vertical -> fromSceneSize.height
+ Orientation.Horizontal -> fromContentSize.width
+ Orientation.Vertical -> fromContentSize.height
}.toFloat()
}
}
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 1fdfca9d9509..48f08a7086d6 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
@@ -26,7 +26,7 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.content.state.TransitionState
-import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
+import com.android.compose.animation.scene.transformation.Transformation
import kotlin.math.tanh
/** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */
@@ -76,7 +76,7 @@ interface SceneTransitionsBuilder {
preview: (TransitionBuilder.() -> Unit)? = null,
reversePreview: (TransitionBuilder.() -> Unit)? = null,
builder: TransitionBuilder.() -> Unit = {},
- ): TransitionSpec
+ )
/**
* Define the animation to be played when transitioning [from] the specified content. For the
@@ -102,7 +102,7 @@ interface SceneTransitionsBuilder {
preview: (TransitionBuilder.() -> Unit)? = null,
reversePreview: (TransitionBuilder.() -> Unit)? = null,
builder: TransitionBuilder.() -> Unit = {},
- ): TransitionSpec
+ )
/**
* Define the animation to be played when the [content] is overscrolled in the given
@@ -115,13 +115,13 @@ interface SceneTransitionsBuilder {
content: ContentKey,
orientation: Orientation,
builder: OverscrollBuilder.() -> Unit,
- ): OverscrollSpec
+ )
/**
* Prevents overscroll the [content] in the given [orientation], allowing ancestors to
* eventually consume the remaining gesture.
*/
- fun overscrollDisabled(content: ContentKey, orientation: Orientation): OverscrollSpec
+ fun overscrollDisabled(content: ContentKey, orientation: Orientation)
}
interface BaseTransitionBuilder : PropertyTransformationBuilder {
@@ -529,15 +529,8 @@ interface PropertyTransformationBuilder {
anchorHeight: Boolean = true,
)
- /**
- * Apply a [CustomPropertyTransformation] to one or more elements.
- *
- * @see com.android.compose.animation.scene.transformation.CustomSizeTransformation
- * @see com.android.compose.animation.scene.transformation.CustomOffsetTransformation
- * @see com.android.compose.animation.scene.transformation.CustomAlphaTransformation
- * @see com.android.compose.animation.scene.transformation.CustomScaleTransformation
- */
- fun transformation(transformation: CustomPropertyTransformation<*>)
+ /** Apply a [transformation] to the element(s) matching [matcher]. */
+ fun transformation(matcher: ElementMatcher, transformation: Transformation.Factory)
}
/** This converter lets you change a linear progress into a function of your choice. */
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 79f8cd47d07d..6742b3200ac4 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
@@ -30,7 +30,6 @@ import androidx.compose.ui.unit.Dp
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transformation.AnchoredSize
import com.android.compose.animation.scene.transformation.AnchoredTranslate
-import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
import com.android.compose.animation.scene.transformation.DrawScale
import com.android.compose.animation.scene.transformation.EdgeTranslate
import com.android.compose.animation.scene.transformation.Fade
@@ -38,8 +37,8 @@ import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.ScaleSize
import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.animation.scene.transformation.Transformation
+import com.android.compose.animation.scene.transformation.TransformationMatcher
import com.android.compose.animation.scene.transformation.TransformationRange
-import com.android.compose.animation.scene.transformation.TransformationWithRange
import com.android.compose.animation.scene.transformation.Translate
internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
@@ -67,8 +66,8 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
preview: (TransitionBuilder.() -> Unit)?,
reversePreview: (TransitionBuilder.() -> Unit)?,
builder: TransitionBuilder.() -> Unit,
- ): TransitionSpec {
- return transition(from = null, to = to, key = key, preview, reversePreview, builder)
+ ) {
+ transition(from = null, to = to, key = key, preview, reversePreview, builder)
}
override fun from(
@@ -78,25 +77,25 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
preview: (TransitionBuilder.() -> Unit)?,
reversePreview: (TransitionBuilder.() -> Unit)?,
builder: TransitionBuilder.() -> Unit,
- ): TransitionSpec {
- return transition(from = from, to = to, key = key, preview, reversePreview, builder)
+ ) {
+ transition(from = from, to = to, key = key, preview, reversePreview, builder)
}
override fun overscroll(
content: ContentKey,
orientation: Orientation,
builder: OverscrollBuilder.() -> Unit,
- ): OverscrollSpec {
+ ) {
val impl = OverscrollBuilderImpl().apply(builder)
- check(impl.transformations.isNotEmpty()) {
+ check(impl.transformationMatchers.isNotEmpty()) {
"This method does not allow empty transformations. " +
"Use overscrollDisabled($content, $orientation) instead."
}
- return overscrollSpec(content, orientation, impl)
+ overscrollSpec(content, orientation, impl)
}
- override fun overscrollDisabled(content: ContentKey, orientation: Orientation): OverscrollSpec {
- return overscrollSpec(content, orientation, OverscrollBuilderImpl())
+ override fun overscrollDisabled(content: ContentKey, orientation: Orientation) {
+ overscrollSpec(content, orientation, OverscrollBuilderImpl())
}
private fun overscrollSpec(
@@ -113,7 +112,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
progressSpec = snap(),
swipeSpec = null,
distance = impl.distance,
- transformations = impl.transformations,
+ transformationMatchers = impl.transformationMatchers,
),
progressConverter = impl.progressConverter,
)
@@ -138,7 +137,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
progressSpec = impl.spec,
swipeSpec = impl.swipeSpec,
distance = impl.distance,
- transformations = impl.transformations,
+ transformationMatchers = impl.transformationMatchers,
)
}
@@ -158,7 +157,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
}
internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
- val transformations = mutableListOf<TransformationWithRange<*>>()
+ val transformationMatchers = mutableListOf<TransformationMatcher>()
private var range: TransformationRange? = null
protected var reversed = false
override var distance: UserActionDistance? = null
@@ -174,23 +173,31 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
range = null
}
- protected fun addTransformation(transformation: Transformation) {
- val transformationWithRange = TransformationWithRange(transformation, range)
- transformations.add(
- if (reversed) {
- transformationWithRange.reversed()
- } else {
- transformationWithRange
- }
+ protected fun addTransformation(
+ matcher: ElementMatcher,
+ transformation: Transformation.Factory,
+ ) {
+ transformationMatchers.add(
+ TransformationMatcher(
+ matcher,
+ transformation,
+ range?.let { range ->
+ if (reversed) {
+ range.reversed()
+ } else {
+ range
+ }
+ },
+ )
)
}
override fun fade(matcher: ElementMatcher) {
- addTransformation(Fade(matcher))
+ addTransformation(matcher, Fade.Factory)
}
override fun translate(matcher: ElementMatcher, x: Dp, y: Dp) {
- addTransformation(Translate(matcher, x, y))
+ addTransformation(matcher, Translate.Factory(x, y))
}
override fun translate(
@@ -198,19 +205,19 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
edge: Edge,
startsOutsideLayoutBounds: Boolean,
) {
- addTransformation(EdgeTranslate(matcher, edge, startsOutsideLayoutBounds))
+ addTransformation(matcher, EdgeTranslate.Factory(edge, startsOutsideLayoutBounds))
}
override fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey) {
- addTransformation(AnchoredTranslate(matcher, anchor))
+ addTransformation(matcher, AnchoredTranslate.Factory(anchor))
}
override fun scaleSize(matcher: ElementMatcher, width: Float, height: Float) {
- addTransformation(ScaleSize(matcher, width, height))
+ addTransformation(matcher, ScaleSize.Factory(width, height))
}
override fun scaleDraw(matcher: ElementMatcher, scaleX: Float, scaleY: Float, pivot: Offset) {
- addTransformation(DrawScale(matcher, scaleX, scaleY, pivot))
+ addTransformation(matcher, DrawScale.Factory(scaleX, scaleY, pivot))
}
override fun anchoredSize(
@@ -219,12 +226,12 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
anchorWidth: Boolean,
anchorHeight: Boolean,
) {
- addTransformation(AnchoredSize(matcher, anchor, anchorWidth, anchorHeight))
+ addTransformation(matcher, AnchoredSize.Factory(anchor, anchorWidth, anchorHeight))
}
- override fun transformation(transformation: CustomPropertyTransformation<*>) {
+ override fun transformation(matcher: ElementMatcher, transformation: Transformation.Factory) {
check(range == null) { "Custom transformations can not be applied inside a range" }
- addTransformation(transformation)
+ addTransformation(matcher, transformation)
}
}
@@ -263,7 +270,10 @@ internal class TransitionBuilderImpl(override val transition: TransitionState.Tr
"(${transition.toContent.debugName})"
}
- addTransformation(SharedElementTransformation(matcher, enabled, elevateInContent))
+ addTransformation(
+ matcher,
+ SharedElementTransformation.Factory(matcher, enabled, elevateInContent),
+ )
}
override fun timestampRange(
@@ -294,6 +304,6 @@ internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), Overscr
x: OverscrollScope.() -> Float,
y: OverscrollScope.() -> Float,
) {
- addTransformation(OverscrollTranslate(matcher, x, y))
+ addTransformation(matcher, OverscrollTranslate.Factory(x, y))
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index 38b1aaa7558d..33f015fe49d9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -401,7 +401,7 @@ sealed interface TransitionState {
else -> null
} ?: return true
- return specForCurrentScene.transformationSpec.transformations.isNotEmpty()
+ return specForCurrentScene.transformationSpec.transformationMatchers.isNotEmpty()
}
internal open fun interruptionProgress(layoutImpl: SceneTransitionLayoutImpl): Float {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
new file mode 100644
index 000000000000..bfb5ca733d90
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
@@ -0,0 +1,289 @@
+/*
+ * 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.compose.animation.scene.reveal
+
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.DeferredTargetAnimation
+import androidx.compose.animation.core.ExperimentalAnimatableApi
+import androidx.compose.animation.core.FiniteAnimationSpec
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.spring
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastCoerceAtLeast
+import androidx.compose.ui.util.fastCoerceAtMost
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.OverlayKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
+import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.animation.scene.transformation.PropertyTransformationScope
+import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
+
+interface ContainerRevealHaptics {
+ /**
+ * Called when the reveal threshold is crossed while the user was dragging on screen.
+ *
+ * Important: This callback is called during layout and its implementation should therefore be
+ * very fast or posted to a different thread.
+ *
+ * @param revealed whether we go from hidden to revealed, i.e. whether the container size is
+ * going to jump from a smaller size to a bigger size.
+ */
+ fun onRevealThresholdCrossed(revealed: Boolean)
+}
+
+/** Animate the reveal of [container] by animating its size. */
+fun TransitionBuilder.verticalContainerReveal(
+ container: ElementKey,
+ haptics: ContainerRevealHaptics,
+) {
+ // Make the swipe distance be exactly the target height of the container.
+ // TODO(b/376438969): Make sure that this works correctly when the target size of the element
+ // is changing during the transition (e.g. a notification was added). At the moment, the user
+ // action distance is only called until it returns a value > 0f, which is then cached.
+ distance = UserActionDistance { fromContent, toContent, _ ->
+ val targetSizeInFromContent = container.targetSize(fromContent)
+ val targetSizeInToContent = container.targetSize(toContent)
+ if (targetSizeInFromContent != null && targetSizeInToContent != null) {
+ error(
+ "verticalContainerReveal should not be used with shared elements, but " +
+ "${container.debugName} is in both ${fromContent.debugName} and " +
+ toContent.debugName
+ )
+ }
+
+ (targetSizeInToContent?.height ?: targetSizeInFromContent?.height)?.toFloat() ?: 0f
+ }
+
+ // TODO(b/376438969): Improve the motion of this gesture using Motion Mechanics.
+
+ // The min distance to swipe before triggering the reveal spring.
+ val distanceThreshold = 80.dp
+
+ // The minimum height of the container.
+ val minHeight = 10.dp
+
+ // The amount removed from the container width at 0% progress.
+ val widthDelta = 140.dp
+
+ // The ratio at which the distance is tracked before reaching the threshold, e.g. if the user
+ // drags 60dp then the height will be 60dp * 0.25f = 15dp.
+ val trackingRatio = 0.25f
+
+ // The max progress starting from which the container should always be visible, even if we are
+ // animating the container out. This is used so that we don't immediately fade out the container
+ // when triggering a one-off animation that hides it.
+ val alphaProgressThreshold = 0.05f
+
+ // The spring animating the size of the container.
+ val sizeSpec = spring<Float>(stiffness = 380f, dampingRatio = 0.9f)
+
+ // The spring animating the alpha of the container.
+ val alphaSpec = spring<Float>(stiffness = 1200f, dampingRatio = 0.99f)
+
+ // The spring animating the progress when releasing the finger.
+ swipeSpec =
+ spring(
+ stiffness = Spring.StiffnessMediumLow,
+ dampingRatio = Spring.DampingRatioNoBouncy,
+ visibilityThreshold = 0.5f,
+ )
+
+ // Size transformation.
+ transformation(container) {
+ VerticalContainerRevealSizeTransformation(
+ haptics,
+ distanceThreshold,
+ trackingRatio,
+ minHeight,
+ widthDelta,
+ sizeSpec,
+ )
+ }
+
+ // Alpha transformation.
+ transformation(container) {
+ ContainerRevealAlphaTransformation(alphaSpec, alphaProgressThreshold)
+ }
+}
+
+@OptIn(ExperimentalAnimatableApi::class)
+private class VerticalContainerRevealSizeTransformation(
+ private val haptics: ContainerRevealHaptics,
+ private val distanceThreshold: Dp,
+ private val trackingRatio: Float,
+ private val minHeight: Dp,
+ private val widthDelta: Dp,
+ private val spec: FiniteAnimationSpec<Float>,
+) : CustomPropertyTransformation<IntSize> {
+ override val property = PropertyTransformation.Property.Size
+
+ private val widthAnimation = DeferredTargetAnimation(Float.VectorConverter)
+ private val heightAnimation = DeferredTargetAnimation(Float.VectorConverter)
+
+ private var previousHasReachedThreshold: Boolean? = null
+
+ override fun PropertyTransformationScope.transform(
+ content: ContentKey,
+ element: ElementKey,
+ transition: TransitionState.Transition,
+ transitionScope: CoroutineScope,
+ ): IntSize {
+ // The distance to go to 100%. Note that we don't use
+ // TransitionState.HasOverscrollProperties.absoluteDistance because the transition will not
+ // implement HasOverscrollProperties if the transition is triggered and not gesture based.
+ val idleSize = checkNotNull(element.targetSize(content))
+ val userActionDistance = idleSize.height
+ val progress =
+ when ((transition as? TransitionState.HasOverscrollProperties)?.bouncingContent) {
+ null -> transition.progressTo(content)
+ content -> 1f
+ else -> 0f
+ }
+ val distance = (progress * userActionDistance).fastCoerceAtLeast(0f)
+ val threshold = distanceThreshold.toPx()
+
+ // Width.
+ val widthDelta = widthDelta.toPx()
+ val width =
+ (idleSize.width - widthDelta +
+ animateSize(
+ size = widthDelta,
+ distance = distance,
+ threshold = threshold,
+ transitionScope = transitionScope,
+ animation = widthAnimation,
+ ))
+ .roundToInt()
+
+ // Height.
+ val minHeight = minHeight.toPx()
+ val height =
+ (
+ // 1) The minimum size of the container.
+ minHeight +
+
+ // 2) The animated size between the minimum size and the threshold.
+ animateSize(
+ size = threshold - minHeight,
+ distance = distance,
+ threshold = threshold,
+ transitionScope = transitionScope,
+ animation = heightAnimation,
+ ) +
+
+ // 3) The remaining height after the threshold, tracking the finger.
+ (distance - threshold).fastCoerceAtLeast(0f))
+ .roundToInt()
+ .fastCoerceAtMost(idleSize.height)
+
+ // Haptics.
+ val hasReachedThreshold = distance >= threshold
+ if (
+ previousHasReachedThreshold != null &&
+ hasReachedThreshold != previousHasReachedThreshold &&
+ transition.isUserInputOngoing
+ ) {
+ haptics.onRevealThresholdCrossed(revealed = hasReachedThreshold)
+ }
+ previousHasReachedThreshold = hasReachedThreshold
+
+ return IntSize(width = width, height = height)
+ }
+
+ /**
+ * Animate a size up to [size], so that it is equal to 0f when distance is 0f and equal to
+ * [size] when `distance >= threshold`, taking the [trackingRatio] into account.
+ */
+ @OptIn(ExperimentalAnimatableApi::class)
+ private fun animateSize(
+ size: Float,
+ distance: Float,
+ threshold: Float,
+ transitionScope: CoroutineScope,
+ animation: DeferredTargetAnimation<Float, AnimationVector1D>,
+ ): Float {
+ val trackingSize = distance.fastCoerceAtMost(threshold) / threshold * size * trackingRatio
+ val springTarget =
+ if (distance >= threshold) {
+ size * (1f - trackingRatio)
+ } else {
+ 0f
+ }
+ val springSize = animation.updateTarget(springTarget, transitionScope, spec)
+ return trackingSize + springSize
+ }
+}
+
+@OptIn(ExperimentalAnimatableApi::class)
+private class ContainerRevealAlphaTransformation(
+ private val spec: FiniteAnimationSpec<Float>,
+ private val progressThreshold: Float,
+) : CustomPropertyTransformation<Float> {
+ override val property = PropertyTransformation.Property.Alpha
+ private val alphaAnimation = DeferredTargetAnimation(Float.VectorConverter)
+
+ override fun PropertyTransformationScope.transform(
+ content: ContentKey,
+ element: ElementKey,
+ transition: TransitionState.Transition,
+ transitionScope: CoroutineScope,
+ ): Float {
+ return alphaAnimation.updateTarget(targetAlpha(transition, content), transitionScope, spec)
+ }
+
+ private fun targetAlpha(transition: TransitionState.Transition, content: ContentKey): Float {
+ if (transition.isUserInputOngoing) {
+ if (transition !is TransitionState.HasOverscrollProperties) {
+ error(
+ "Unsupported transition driven by user input but that does not have " +
+ "overscroll properties: $transition"
+ )
+ }
+
+ val bouncingContent = transition.bouncingContent
+ return if (bouncingContent != null) {
+ if (bouncingContent == content) 1f else 0f
+ } else {
+ if (transition.progressTo(content) > 0f) 1f else 0f
+ }
+ }
+
+ // The transition was committed (the user released their finger), so the alpha depends on
+ // whether we are animating towards the content (showing the container) or away from it
+ // (hiding the container).
+ val isShowingContainer =
+ when (content) {
+ is SceneKey -> transition.currentScene == content
+ is OverlayKey -> transition.currentOverlays.contains(content)
+ }
+
+ return if (isShowingContainer || transition.progressTo(content) >= progressThreshold) {
+ 1f
+ } else {
+ 0f
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
index 85bb5336d574..6575068201d8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
@@ -19,16 +19,17 @@ package com.android.compose.animation.scene.transformation
import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.content.state.TransitionState
/** Anchor the size of an element to the size of another element. */
-internal class AnchoredSize(
- override val matcher: ElementMatcher,
+internal class AnchoredSize
+private constructor(
private val anchor: ElementKey,
private val anchorWidth: Boolean,
private val anchorHeight: Boolean,
-) : InterpolatedSizeTransformation {
+) : InterpolatedPropertyTransformation<IntSize> {
+ override val property = PropertyTransformation.Property.Size
+
override fun PropertyTransformationScope.transform(
content: ContentKey,
element: ElementKey,
@@ -59,4 +60,12 @@ internal class AnchoredSize(
anchorSizeIn(transition.fromContent)
}
}
+
+ class Factory(
+ private val anchor: ElementKey,
+ private val anchorWidth: Boolean,
+ private val anchorHeight: Boolean,
+ ) : Transformation.Factory {
+ override fun create(): Transformation = AnchoredSize(anchor, anchorWidth, anchorHeight)
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
index 04cd68344252..890902b7ab67 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
@@ -19,14 +19,13 @@ package com.android.compose.animation.scene.transformation
import androidx.compose.ui.geometry.Offset
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.content.state.TransitionState
/** Anchor the translation of an element to another element. */
-internal class AnchoredTranslate(
- override val matcher: ElementMatcher,
- private val anchor: ElementKey,
-) : InterpolatedOffsetTransformation {
+internal class AnchoredTranslate private constructor(private val anchor: ElementKey) :
+ InterpolatedPropertyTransformation<Offset> {
+ override val property = PropertyTransformation.Property.Offset
+
override fun PropertyTransformationScope.transform(
content: ContentKey,
element: ElementKey,
@@ -56,6 +55,10 @@ internal class AnchoredTranslate(
Offset(idleValue.x + offset.x, idleValue.y + offset.y)
}
}
+
+ class Factory(private val anchor: ElementKey) : Transformation.Factory {
+ override fun create(): Transformation = AnchoredTranslate(anchor)
+ }
}
internal fun throwMissingAnchorException(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
index 45d6d4037c49..347f1c325058 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
@@ -19,7 +19,6 @@ package com.android.compose.animation.scene.transformation
import androidx.compose.ui.geometry.Offset
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.Scale
import com.android.compose.animation.scene.content.state.TransitionState
@@ -27,12 +26,14 @@ import com.android.compose.animation.scene.content.state.TransitionState
* Scales the draw size of an element. Note this will only scale the draw inside of an element,
* therefore it won't impact layout of elements around it.
*/
-internal class DrawScale(
- override val matcher: ElementMatcher,
+internal class DrawScale
+private constructor(
private val scaleX: Float,
private val scaleY: Float,
- private val pivot: Offset = Offset.Unspecified,
-) : InterpolatedScaleTransformation {
+ private val pivot: Offset,
+) : InterpolatedPropertyTransformation<Scale> {
+ override val property = PropertyTransformation.Property.Scale
+
override fun PropertyTransformationScope.transform(
content: ContentKey,
element: ElementKey,
@@ -41,4 +42,9 @@ internal class DrawScale(
): Scale {
return Scale(scaleX, scaleY, pivot)
}
+
+ class Factory(private val scaleX: Float, private val scaleY: Float, private val pivot: Offset) :
+ Transformation.Factory {
+ override fun create(): Transformation = DrawScale(scaleX, scaleY, pivot)
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index 21d66d784e2d..f8e6dc09ce75 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -20,15 +20,14 @@ import androidx.compose.ui.geometry.Offset
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.content.state.TransitionState
/** Translate an element from an edge of the layout. */
-internal class EdgeTranslate(
- override val matcher: ElementMatcher,
- private val edge: Edge,
- private val startsOutsideLayoutBounds: Boolean = true,
-) : InterpolatedOffsetTransformation {
+internal class EdgeTranslate
+private constructor(private val edge: Edge, private val startsOutsideLayoutBounds: Boolean) :
+ InterpolatedPropertyTransformation<Offset> {
+ override val property = PropertyTransformation.Property.Offset
+
override fun PropertyTransformationScope.transform(
content: ContentKey,
element: ElementKey,
@@ -67,4 +66,9 @@ internal class EdgeTranslate(
}
}
}
+
+ class Factory(private val edge: Edge, private val startsOutsideLayoutBounds: Boolean) :
+ Transformation.Factory {
+ override fun create(): Transformation = EdgeTranslate(edge, startsOutsideLayoutBounds)
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
index d942273ab9ab..d92419ef368d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
@@ -18,11 +18,12 @@ package com.android.compose.animation.scene.transformation
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.content.state.TransitionState
/** Fade an element in or out. */
-internal class Fade(override val matcher: ElementMatcher) : InterpolatedAlphaTransformation {
+internal object Fade : InterpolatedPropertyTransformation<Float> {
+ override val property = PropertyTransformation.Property.Alpha
+
override fun PropertyTransformationScope.transform(
content: ContentKey,
element: ElementKey,
@@ -33,4 +34,8 @@ internal class Fade(override val matcher: ElementMatcher) : InterpolatedAlphaTra
// fading out, which is `0` in both cases.
return 0f
}
+
+ object Factory : Transformation.Factory {
+ override fun create(): Transformation = Fade
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
index 5f3cdab3c572..5d31fd9ca196 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
@@ -19,7 +19,6 @@ package com.android.compose.animation.scene.transformation
import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.content.state.TransitionState
import kotlin.math.roundToInt
@@ -27,11 +26,10 @@ import kotlin.math.roundToInt
* Scales the size of an element. Note that this makes the element resize every frame and will
* therefore impact the layout of other elements.
*/
-internal class ScaleSize(
- override val matcher: ElementMatcher,
- private val width: Float = 1f,
- private val height: Float = 1f,
-) : InterpolatedSizeTransformation {
+internal class ScaleSize private constructor(private val width: Float, private val height: Float) :
+ InterpolatedPropertyTransformation<IntSize> {
+ override val property = PropertyTransformation.Property.Size
+
override fun PropertyTransformationScope.transform(
content: ContentKey,
element: ElementKey,
@@ -43,4 +41,9 @@ internal class ScaleSize(
height = (idleValue.height * height).roundToInt(),
)
}
+
+ class Factory(private val width: Float = 1f, private val height: Float = 1f) :
+ Transformation.Factory {
+ override fun create(): Transformation = ScaleSize(width, height)
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index d5143d729f2e..e0b42189854a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -35,26 +35,59 @@ import kotlinx.coroutines.CoroutineScope
/** A transformation applied to one or more elements during a transition. */
sealed interface Transformation {
- /**
- * The matcher that should match the element(s) to which this transformation should be applied.
- */
- val matcher: ElementMatcher
+ fun interface Factory {
+ fun create(): Transformation
+ }
}
-internal class SharedElementTransformation(
- override val matcher: ElementMatcher,
+// Important: SharedElementTransformation must be a data class because we check that we don't
+// provide 2 different transformations for the same element in Element.kt
+internal data class SharedElementTransformation(
internal val enabled: Boolean,
internal val elevateInContent: ContentKey?,
-) : Transformation
+) : Transformation {
+ class Factory(
+ internal val matcher: ElementMatcher,
+ internal val enabled: Boolean,
+ internal val elevateInContent: ContentKey?,
+ ) : Transformation.Factory {
+ override fun create(): Transformation {
+ return SharedElementTransformation(enabled, elevateInContent)
+ }
+ }
+}
+
+/**
+ * A transformation that changes the value of an element [Property], like its [size][Property.Size]
+ * or [offset][Property.Offset].
+ */
+sealed interface PropertyTransformation<T> : Transformation {
+ /** The property to which this transformation is applied. */
+ val property: Property<T>
+
+ sealed class Property<T> {
+ /** The size of an element. */
+ data object Size : Property<IntSize>()
+
+ /** The offset (position) of an element. */
+ data object Offset : Property<androidx.compose.ui.geometry.Offset>()
+
+ /** The alpha of an element. */
+ data object Alpha : Property<Float>()
-/** A transformation that changes the value of an element property, like its size or offset. */
-sealed interface PropertyTransformation<T> : Transformation
+ /**
+ * The drawing scale of an element. Animating the scale does not have any effect on the
+ * layout.
+ */
+ data object Scale : Property<com.android.compose.animation.scene.Scale>()
+ }
+}
/**
* A transformation to a target/transformed value that is automatically interpolated using the
* transition progress and transformation range.
*/
-sealed interface InterpolatedPropertyTransformation<T> : PropertyTransformation<T> {
+interface InterpolatedPropertyTransformation<T> : PropertyTransformation<T> {
/**
* Return the transformed value for the given property, i.e.:
* - the value at progress = 0% for elements that are entering the layout (i.e. elements in the
@@ -73,19 +106,7 @@ sealed interface InterpolatedPropertyTransformation<T> : PropertyTransformation<
): T
}
-/** An [InterpolatedPropertyTransformation] applied to the size of one or more elements. */
-interface InterpolatedSizeTransformation : InterpolatedPropertyTransformation<IntSize>
-
-/** An [InterpolatedPropertyTransformation] applied to the offset of one or more elements. */
-interface InterpolatedOffsetTransformation : InterpolatedPropertyTransformation<Offset>
-
-/** An [InterpolatedPropertyTransformation] applied to the alpha of one or more elements. */
-interface InterpolatedAlphaTransformation : InterpolatedPropertyTransformation<Float>
-
-/** An [InterpolatedPropertyTransformation] applied to the scale of one or more elements. */
-interface InterpolatedScaleTransformation : InterpolatedPropertyTransformation<Scale>
-
-sealed interface CustomPropertyTransformation<T> : PropertyTransformation<T> {
+interface CustomPropertyTransformation<T> : PropertyTransformation<T> {
/**
* Return the value that the property should have in the current frame for the given [content]
* and [element].
@@ -105,25 +126,20 @@ sealed interface CustomPropertyTransformation<T> : PropertyTransformation<T> {
): T
}
-/** A [CustomPropertyTransformation] applied to the size of one or more elements. */
-interface CustomSizeTransformation : CustomPropertyTransformation<IntSize>
-
-/** A [CustomPropertyTransformation] applied to the offset of one or more elements. */
-interface CustomOffsetTransformation : CustomPropertyTransformation<Offset>
-
-/** A [CustomPropertyTransformation] applied to the alpha of one or more elements. */
-interface CustomAlphaTransformation : CustomPropertyTransformation<Float>
-
-/** A [CustomPropertyTransformation] applied to the scale of one or more elements. */
-interface CustomScaleTransformation : CustomPropertyTransformation<Scale>
-
interface PropertyTransformationScope : Density, ElementStateScope {
/** The current [direction][LayoutDirection] of the layout. */
val layoutDirection: LayoutDirection
}
+/** Defines the transformation-type to be applied to all elements matching [matcher]. */
+internal class TransformationMatcher(
+ val matcher: ElementMatcher,
+ val factory: Transformation.Factory,
+ val range: TransformationRange?,
+)
+
/** A pair consisting of a [transformation] and optional [range]. */
-class TransformationWithRange<out T : Transformation>(
+internal data class TransformationWithRange<out T : Transformation>(
val transformation: T,
val range: TransformationRange?,
) {
@@ -135,7 +151,7 @@ class TransformationWithRange<out T : Transformation>(
}
/** The progress-based range of a [PropertyTransformation]. */
-data class TransformationRange(val start: Float, val end: Float, val easing: Easing) {
+internal data class TransformationRange(val start: Float, val end: Float, val easing: Easing) {
constructor(
start: Float? = null,
end: Float? = null,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index d756c86f680c..2f4d5bff8b41 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -19,18 +19,15 @@ package com.android.compose.animation.scene.transformation
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.OverscrollScope
import com.android.compose.animation.scene.content.state.TransitionState
-internal class Translate(
- override val matcher: ElementMatcher,
- private val x: Dp = 0.dp,
- private val y: Dp = 0.dp,
-) : InterpolatedOffsetTransformation {
+internal class Translate private constructor(private val x: Dp, private val y: Dp) :
+ InterpolatedPropertyTransformation<Offset> {
+ override val property = PropertyTransformation.Property.Offset
+
override fun PropertyTransformationScope.transform(
content: ContentKey,
element: ElementKey,
@@ -39,13 +36,19 @@ internal class Translate(
): Offset {
return Offset(idleValue.x + x.toPx(), idleValue.y + y.toPx())
}
+
+ class Factory(private val x: Dp, private val y: Dp) : Transformation.Factory {
+ override fun create(): Transformation = Translate(x, y)
+ }
}
-internal class OverscrollTranslate(
- override val matcher: ElementMatcher,
- val x: OverscrollScope.() -> Float = { 0f },
- val y: OverscrollScope.() -> Float = { 0f },
-) : InterpolatedOffsetTransformation {
+internal class OverscrollTranslate
+private constructor(
+ private val x: OverscrollScope.() -> Float,
+ private val y: OverscrollScope.() -> Float,
+) : InterpolatedPropertyTransformation<Offset> {
+ override val property = PropertyTransformation.Property.Offset
+
private val cachedOverscrollScope = CachedOverscrollScope()
override fun PropertyTransformationScope.transform(
@@ -63,6 +66,13 @@ internal class OverscrollTranslate(
return Offset(x = value.x + overscrollScope.x(), y = value.y + overscrollScope.y())
}
+
+ class Factory(
+ private val x: OverscrollScope.() -> Float,
+ private val y: OverscrollScope.() -> Float,
+ ) : Transformation.Factory {
+ override fun create(): Transformation = OverscrollTranslate(x, y)
+ }
}
/**
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 61e9bb0db0f7..10057b280d28 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -644,7 +644,7 @@ class DraggableHandlerTest {
mutableUserActionsA = emptyMap()
mutableUserActionsB = emptyMap()
- // start accelaratedScroll and scroll over to B -> null
+ // start acceleratedScroll and scroll over to B -> null
val dragController2 = onDragStartedImmediately()
dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
@@ -1530,7 +1530,7 @@ class DraggableHandlerTest {
fun interceptingTransitionKeepsDistance() = runGestureTest {
var swipeDistance = 75f
layoutState.transitions = transitions {
- from(SceneA, to = SceneB) { distance = UserActionDistance { _, _ -> swipeDistance } }
+ from(SceneA, to = SceneB) { distance = UserActionDistance { _, _, _ -> swipeDistance } }
}
// Start transition.
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 57ad81ebf7f0..79ca891babd1 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
@@ -153,7 +153,7 @@ class SceneTransitionLayoutStateTest {
// Default transition from A to B.
assertThat(state.setTargetScene(SceneB, animationScope = this)).isNotNull()
- assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(1)
+ assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(1)
// Go back to A.
state.setTargetScene(SceneA, animationScope = this)
@@ -166,7 +166,7 @@ class SceneTransitionLayoutStateTest {
state.setTargetScene(SceneB, animationScope = this, transitionKey = transitionkey)
)
.isNotNull()
- assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(2)
+ assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(2)
}
@Test
@@ -686,4 +686,30 @@ class SceneTransitionLayoutStateTest {
assertThat(state.transitionState).isIdle()
assertThat(job.isCancelled).isTrue()
}
+
+ @Test
+ fun badTransitionSpecThrowsMeaningfulMessageWhenStartingTransition() {
+ val state =
+ MutableSceneTransitionLayoutState(
+ SceneA,
+ transitions {
+ // This transition definition is bad because they both match when transitioning
+ // from A to B.
+ from(SceneA) {}
+ to(SceneB) {}
+ },
+ )
+
+ val exception =
+ assertThrows(IllegalStateException::class.java) {
+ runBlocking { state.startTransition(transition(from = SceneA, to = SceneB)) }
+ }
+
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo(
+ "Found multiple transition specs for transition SceneKey(debugName=SceneA) => " +
+ "SceneKey(debugName=SceneB)"
+ )
+ }
}
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 97a96a4333cb..b3a3261122a8 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
@@ -55,7 +55,6 @@ import androidx.compose.ui.test.swipeRight
import androidx.compose.ui.test.swipeUp
import androidx.compose.ui.test.swipeWithVelocity
import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -672,12 +671,12 @@ class SwipeToSceneTest {
}
assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
- assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(1)
+ assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).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 = SceneA, to = SceneB)).isTrue()
- assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(2)
+ assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(2)
}
@Test
@@ -685,7 +684,8 @@ class SwipeToSceneTest {
val swipeDistance =
object : UserActionDistance {
override fun UserActionDistanceScope.absoluteDistance(
- fromSceneSize: IntSize,
+ fromContent: ContentKey,
+ toContent: ContentKey,
orientation: Orientation,
): Float {
// Foo is going to have a vertical offset of 50dp. Let's make the swipe distance
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 1f9ba9ee9372..70f2ff80f9d7 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -28,11 +28,12 @@ import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.content.state.TransitionState
-import com.android.compose.animation.scene.transformation.CustomSizeTransformation
+import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
import com.android.compose.animation.scene.transformation.OverscrollTranslate
+import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.PropertyTransformationScope
+import com.android.compose.animation.scene.transformation.TransformationMatcher
import com.android.compose.animation.scene.transformation.TransformationRange
-import com.android.compose.animation.scene.transformation.TransformationWithRange
import com.android.compose.test.transition
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
@@ -103,7 +104,7 @@ class TransitionDslTest {
val transitions = transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } }
val transformations =
- transitions.transitionSpecs.single().transformationSpec(aToB()).transformations
+ transitions.transitionSpecs.single().transformationSpec(aToB()).transformationMatchers
assertThat(transformations.size).isEqualTo(1)
assertThat(transformations.single().range).isEqualTo(null)
}
@@ -126,7 +127,7 @@ class TransitionDslTest {
}
val transformations =
- transitions.transitionSpecs.single().transformationSpec(aToB()).transformations
+ transitions.transitionSpecs.single().transformationSpec(aToB()).transformationMatchers
assertThat(transformations)
.comparingElementsUsing(TRANSFORMATION_RANGE)
.containsExactly(
@@ -157,7 +158,7 @@ class TransitionDslTest {
}
val transformations =
- transitions.transitionSpecs.single().transformationSpec(aToB()).transformations
+ transitions.transitionSpecs.single().transformationSpec(aToB()).transformationMatchers
assertThat(transformations)
.comparingElementsUsing(TRANSFORMATION_RANGE)
.containsExactly(
@@ -185,7 +186,7 @@ class TransitionDslTest {
}
val transformations =
- transitions.transitionSpecs.single().transformationSpec(aToB()).transformations
+ transitions.transitionSpecs.single().transformationSpec(aToB()).transformationMatchers
assertThat(transformations)
.comparingElementsUsing(TRANSFORMATION_RANGE)
.containsExactly(
@@ -215,7 +216,7 @@ class TransitionDslTest {
// to B we defined.
val transitionSpec = transitions.transitionSpec(from = SceneB, to = SceneA, key = null)
- val transformations = transitionSpec.transformationSpec(aToB()).transformations
+ val transformations = transitionSpec.transformationSpec(aToB()).transformationMatchers
assertThat(transformations)
.comparingElementsUsing(TRANSFORMATION_RANGE)
@@ -225,7 +226,7 @@ class TransitionDslTest {
)
val previewTransformations =
- transitionSpec.previewTransformationSpec(aToB())?.transformations
+ transitionSpec.previewTransformationSpec(aToB())?.transformationMatchers
assertThat(previewTransformations)
.comparingElementsUsing(TRANSFORMATION_RANGE)
@@ -255,7 +256,7 @@ class TransitionDslTest {
key = TransitionKey.PredictiveBack,
)
- val transformations = transitionSpec.transformationSpec(aToB()).transformations
+ val transformations = transitionSpec.transformationSpec(aToB()).transformationMatchers
assertThat(transformations)
.comparingElementsUsing(TRANSFORMATION_RANGE)
@@ -265,7 +266,7 @@ class TransitionDslTest {
)
val previewTransformations =
- transitionSpec.previewTransformationSpec(aToB())?.transformations
+ transitionSpec.previewTransformationSpec(aToB())?.transformationMatchers
assertThat(previewTransformations)
.comparingElementsUsing(TRANSFORMATION_RANGE)
@@ -316,7 +317,7 @@ class TransitionDslTest {
val overscrollSpec = transitions.overscrollSpecs.single()
val transformation =
- overscrollSpec.transformationSpec.transformations.single().transformation
+ overscrollSpec.transformationSpec.transformationMatchers.single().factory.create()
assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java)
}
@@ -324,7 +325,7 @@ class TransitionDslTest {
fun overscrollSpec_for_overscrollDisabled() {
val transitions = transitions { overscrollDisabled(SceneA, Orientation.Vertical) }
val overscrollSpec = transitions.overscrollSpecs.single()
- assertThat(overscrollSpec.transformationSpec.transformations).isEmpty()
+ assertThat(overscrollSpec.transformationSpec.transformationMatchers).isEmpty()
}
@Test
@@ -353,9 +354,9 @@ class TransitionDslTest {
val transitions = transitions {
from(SceneA, to = SceneB) {
fractionRange {
- transformation(
- object : CustomSizeTransformation {
- override val matcher: ElementMatcher = TestElements.Foo
+ transformation(TestElements.Foo) {
+ object : CustomPropertyTransformation<IntSize> {
+ override val property = PropertyTransformation.Property.Size
override fun PropertyTransformationScope.transform(
content: ContentKey,
@@ -364,7 +365,7 @@ class TransitionDslTest {
transitionScope: CoroutineScope,
): IntSize = IntSize.Zero
}
- )
+ }
}
}
}
@@ -377,7 +378,7 @@ class TransitionDslTest {
companion object {
private val TRANSFORMATION_RANGE =
- Correspondence.transforming<TransformationWithRange<*>, TransformationRange?>(
+ Correspondence.transforming<TransformationMatcher, TransformationRange?>(
{ it?.range },
"has range equal to",
)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
index 313379f4c74b..0adb4809dd2d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
@@ -157,7 +157,7 @@ abstract class BaseTransitionSubject<T : TransitionState.Transition>(
check("isUserInputOngoing").that(actual.isUserInputOngoing).isEqualTo(isUserInputOngoing)
}
- fun hasOverscrollSpec(): OverscrollSpec {
+ internal fun hasOverscrollSpec(): OverscrollSpec {
check("currentOverscrollSpec").that(actual.currentOverscrollSpec).isNotNull()
return actual.currentOverscrollSpec!!
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt
index 487b0992c3e5..444ec4ead561 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt
@@ -30,7 +30,6 @@ import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.TestElements
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.testTransition
@@ -47,8 +46,9 @@ class CustomTransformationTest {
@Test
fun customSize() {
/** A size transformation that adds [add] to the size of the transformed element(s). */
- class AddSizeTransformation(override val matcher: ElementMatcher, private val add: Dp) :
- CustomSizeTransformation {
+ class AddSizeTransformation(private val add: Dp) : CustomPropertyTransformation<IntSize> {
+ override val property = PropertyTransformation.Property.Size
+
override fun PropertyTransformationScope.transform(
content: ContentKey,
element: ElementKey,
@@ -69,7 +69,7 @@ class CustomTransformationTest {
spec = tween(16 * 4, easing = LinearEasing)
// Add 80dp to the width and height of Foo.
- transformation(AddSizeTransformation(TestElements.Foo, 80.dp))
+ transformation(TestElements.Foo) { AddSizeTransformation(80.dp) }
},
) {
before { onElement(TestElements.Foo).assertSizeIsEqualTo(40.dp, 20.dp) }
@@ -84,8 +84,9 @@ class CustomTransformationTest {
@Test
fun customOffset() {
/** An offset transformation that adds [add] to the offset of the transformed element(s). */
- class AddOffsetTransformation(override val matcher: ElementMatcher, private val add: Dp) :
- CustomOffsetTransformation {
+ class AddOffsetTransformation(private val add: Dp) : CustomPropertyTransformation<Offset> {
+ override val property = PropertyTransformation.Property.Offset
+
override fun PropertyTransformationScope.transform(
content: ContentKey,
element: ElementKey,
@@ -106,7 +107,7 @@ class CustomTransformationTest {
spec = tween(16 * 4, easing = LinearEasing)
// Add 80dp to the offset of Foo.
- transformation(AddOffsetTransformation(TestElements.Foo, 80.dp))
+ transformation(TestElements.Foo) { AddOffsetTransformation(80.dp) }
},
) {
before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(0.dp, 0.dp) }
diff --git a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
index 651e401681c6..18073adf9e7a 100644
--- a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
@@ -16,4 +16,5 @@
<resources>
<dimen name="keyguard_smartspace_top_offset">0dp</dimen>
+ <dimen name="status_view_margin_horizontal">8dp</dimen>
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw600dp/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
index 10e630d44488..37cd590b979e 100644
--- a/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
@@ -17,4 +17,5 @@
<resources>
<!-- For portrait direction in unfold foldable device, we don't need keyguard_smartspace_top_offset-->
<dimen name="keyguard_smartspace_top_offset">0dp</dimen>
+ <dimen name="status_view_margin_horizontal">62dp</dimen>
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 000000000000..c1cf42c4b0f0
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <dimen name="status_view_margin_horizontal">24dp</dimen>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/customization/res/values-sw720dp-port/dimens.xml
new file mode 100644
index 000000000000..54dbeaa1a3dd
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-sw720dp-port/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <dimen name="status_view_margin_horizontal">124dp</dimen>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
index 7feea6e5e8dd..041ae62670e5 100644
--- a/packages/SystemUI/customization/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -39,4 +39,5 @@
<!--Dimens used in both lockscreen preview and smartspace -->
<dimen name="date_weather_view_height">24dp</dimen>
<dimen name="enhanced_smartspace_height">104dp</dimen>
+ <dimen name="status_view_margin_horizontal">0dp</dimen>
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt
deleted file mode 100644
index 2a2d33308d12..000000000000
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.clocks
-
-import android.content.Context
-import android.content.res.Resources
-import android.graphics.Typeface
-import android.graphics.drawable.Drawable
-import android.util.TypedValue
-import com.android.internal.policy.SystemBarUtils
-import com.android.systemui.log.core.Logger
-import com.android.systemui.log.core.MessageBuffer
-import com.android.systemui.monet.Style as MonetStyle
-import java.io.IOException
-
-class AssetLoader
-private constructor(
- private val pluginCtx: Context,
- private val sysuiCtx: Context,
- private val baseDir: String,
- var seedColor: Int?,
- var overrideChroma: Float?,
- val typefaceCache: TypefaceCache,
- messageBuffer: MessageBuffer,
-) {
- val logger = Logger(messageBuffer, TAG)
- private val resources =
- listOf(
- Pair(pluginCtx.resources, pluginCtx.packageName),
- Pair(sysuiCtx.resources, sysuiCtx.packageName),
- )
-
- constructor(
- pluginCtx: Context,
- sysuiCtx: Context,
- baseDir: String,
- messageBuffer: MessageBuffer,
- ) : this(
- pluginCtx,
- sysuiCtx,
- baseDir,
- seedColor = null,
- overrideChroma = null,
- typefaceCache =
- TypefaceCache(messageBuffer) {
- // TODO(b/364680873): Move constant to config_clockFontFamily when shipping
- return@TypefaceCache Typeface.create("google-sans-flex-clock", Typeface.NORMAL)
- },
- messageBuffer = messageBuffer,
- )
-
- fun listAssets(path: String): List<String> {
- return pluginCtx.resources.assets.list("$baseDir$path")?.toList() ?: emptyList()
- }
-
- fun tryReadString(resStr: String): String? = tryRead(resStr, ::readString)
-
- fun readString(resStr: String): String {
- val resPair = resolveResourceId(resStr)
- if (resPair == null) {
- throw IOException("Failed to parse string: $resStr")
- }
-
- val (res, id) = resPair
- return res.getString(id)
- }
-
- fun readFontAsset(resStr: String): Typeface = typefaceCache.getTypeface(resStr)
-
- fun tryReadTextAsset(path: String?): String? = tryRead(path, ::readTextAsset)
-
- fun readTextAsset(path: String): String {
- return pluginCtx.resources.assets.open("$baseDir$path").use { stream ->
- val buffer = ByteArray(stream.available())
- stream.read(buffer)
- String(buffer)
- }
- }
-
- fun tryReadDrawableAsset(path: String?): Drawable? = tryRead(path, ::readDrawableAsset)
-
- fun readDrawableAsset(path: String): Drawable {
- var result: Drawable?
-
- if (path.startsWith("@")) {
- val pair = resolveResourceId(path)
- if (pair == null) {
- throw IOException("Failed to parse $path to an id")
- }
- val (res, id) = pair
- result = res.getDrawable(id)
- } else if (path.endsWith("xml")) {
- // TODO(b/248609434): Support xml files in assets
- throw IOException("Cannot load xml files from assets")
- } else {
- // Attempt to load as if it's a bitmap and directly loadable
- result =
- pluginCtx.resources.assets.open("$baseDir$path").use { stream ->
- Drawable.createFromResourceStream(
- pluginCtx.resources,
- TypedValue(),
- stream,
- null,
- )
- }
- }
-
- return result ?: throw IOException("Failed to load: $baseDir$path")
- }
-
- fun parseResourceId(resStr: String): Triple<String?, String, String> {
- if (!resStr.startsWith("@")) {
- throw IOException("Invalid resource id: $resStr; Must start with '@'")
- }
-
- // Parse out resource string
- val parts = resStr.drop(1).split('/', ':')
- return when (parts.size) {
- 2 -> Triple(null, parts[0], parts[1])
- 3 -> Triple(parts[0], parts[1], parts[2])
- else -> throw IOException("Failed to parse resource string: $resStr")
- }
- }
-
- fun resolveResourceId(resStr: String): Pair<Resources, Int>? {
- val (packageName, category, name) = parseResourceId(resStr)
- return resolveResourceId(packageName, category, name)
- }
-
- fun resolveResourceId(
- packageName: String?,
- category: String,
- name: String,
- ): Pair<Resources, Int>? {
- for ((res, ctxPkgName) in resources) {
- val result = res.getIdentifier(name, category, packageName ?: ctxPkgName)
- if (result != 0) {
- return Pair(res, result)
- }
- }
- return null
- }
-
- private fun <TArg : Any, TRes : Any> tryRead(arg: TArg?, fn: (TArg) -> TRes): TRes? {
- try {
- if (arg == null) {
- return null
- }
- return fn(arg)
- } catch (ex: IOException) {
- logger.w("Failed to read $arg", ex)
- return null
- }
- }
-
- fun assetExists(path: String): Boolean {
- try {
- if (path.startsWith("@")) {
- val pair = resolveResourceId(path)
- return pair != null
- } else {
- val stream = pluginCtx.resources.assets.open("$baseDir$path")
- if (stream == null) {
- return false
- }
-
- stream.close()
- return true
- }
- } catch (ex: IOException) {
- return false
- }
- }
-
- fun copy(messageBuffer: MessageBuffer? = null): AssetLoader =
- AssetLoader(
- pluginCtx,
- sysuiCtx,
- baseDir,
- seedColor,
- overrideChroma,
- typefaceCache,
- messageBuffer ?: logger.buffer,
- )
-
- fun setSeedColor(seedColor: Int?, style: MonetStyle?) {
- this.seedColor = seedColor
- }
-
- fun getClockPaddingStart(): Int {
- val result = resolveResourceId(null, "dimen", "clock_padding_start")
- if (result != null) {
- val (res, id) = result
- return res.getDimensionPixelSize(id)
- }
- return -1
- }
-
- fun getStatusBarHeight(): Int {
- val display = pluginCtx.getDisplayNoVerify()
- if (display != null) {
- return SystemBarUtils.getStatusBarHeight(pluginCtx.resources, display.cutout)
- }
-
- logger.w("No display available; falling back to android.R.dimen.status_bar_height")
- val statusBarHeight = resolveResourceId("android", "dimen", "status_bar_height")
- if (statusBarHeight != null) {
- val (res, resId) = statusBarHeight
- return res.getDimensionPixelSize(resId)
- }
-
- throw Exception("Could not fetch StatusBarHeight")
- }
-
- fun getResourcesId(name: String): Int = getResource("id", name) { _, id -> id }
-
- fun getDimen(name: String): Int = getResource("dimen", name, Resources::getDimensionPixelSize)
-
- fun getString(name: String): String = getResource("string", name, Resources::getString)
-
- private fun <T> getResource(
- category: String,
- name: String,
- getter: (res: Resources, id: Int) -> T,
- ): T {
- val result = resolveResourceId(null, category, name)
- if (result != null) {
- val (res, id) = result
- if (id == -1) throw Exception("Cannot find id of $id from $TAG")
- return getter(res, id)
- }
- throw Exception("Cannot find id of $name from $TAG")
- }
-
- companion object {
- private val TAG = AssetLoader::class.simpleName!!
- }
-}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
index 4ed8fd8f631d..d0a32dcf9865 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
@@ -16,12 +16,9 @@
package com.android.systemui.shared.clocks
-import android.content.Context
-import android.content.res.Resources
import android.graphics.Rect
import androidx.annotation.VisibleForTesting
import com.android.systemui.log.core.Logger
-import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.ClockAnimations
import com.android.systemui.plugins.clocks.ClockEvents
@@ -37,31 +34,22 @@ import java.util.Locale
import java.util.TimeZone
class ComposedDigitalLayerController(
- private val ctx: Context,
- private val resources: Resources,
- private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources
+ private val clockCtx: ClockContext,
private val layer: ComposedDigitalHandLayer,
- messageBuffer: MessageBuffer,
) : SimpleClockLayerController {
- private val logger = Logger(messageBuffer, ComposedDigitalLayerController::class.simpleName!!)
+ private val logger =
+ Logger(clockCtx.messageBuffer, ComposedDigitalLayerController::class.simpleName!!)
val layerControllers = mutableListOf<SimpleClockLayerController>()
val dozeState = DefaultClockController.AnimationState(1F)
- override val view = FlexClockView(ctx, assets, messageBuffer)
+ override val view = FlexClockView(clockCtx)
init {
layer.digitalLayers.forEach {
- val childView = SimpleDigitalClockTextView(ctx, messageBuffer)
+ val childView = SimpleDigitalClockTextView(clockCtx)
val controller =
- SimpleDigitalHandLayerController(
- ctx,
- resources,
- assets,
- it as DigitalHandLayer,
- childView,
- messageBuffer,
- )
+ SimpleDigitalHandLayerController(clockCtx, it as DigitalHandLayer, childView)
view.addView(childView)
layerControllers.add(controller)
@@ -156,8 +144,9 @@ class ComposedDigitalLayerController(
val color =
when {
theme.seedColor != null -> theme.seedColor!!
- theme.isDarkTheme -> resources.getColor(android.R.color.system_accent1_100)
- else -> resources.getColor(android.R.color.system_accent2_600)
+ theme.isDarkTheme ->
+ clockCtx.resources.getColor(android.R.color.system_accent1_100)
+ else -> clockCtx.resources.getColor(android.R.color.system_accent2_600)
}
view.updateColor(color)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index be4ebdfcb992..5317ac1dddcd 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -15,10 +15,10 @@ package com.android.systemui.shared.clocks
import android.content.Context
import android.content.res.Resources
+import android.graphics.Typeface
import android.view.LayoutInflater
import com.android.systemui.customization.R
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.core.LogcatOnlyMessageBuffer
+import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFontAxis
import com.android.systemui.plugins.clocks.ClockFontAxisSetting
@@ -33,6 +33,15 @@ import com.android.systemui.shared.clocks.view.VerticalAlignment
private val TAG = DefaultClockProvider::class.simpleName
const val DEFAULT_CLOCK_ID = "DEFAULT"
+data class ClockContext(
+ val context: Context,
+ val resources: Resources,
+ val settings: ClockSettings,
+ val typefaceCache: TypefaceCache,
+ val messageBuffers: ClockMessageBuffers,
+ val messageBuffer: MessageBuffer,
+)
+
/** Provides the default system clock */
class DefaultClockProvider(
val ctx: Context,
@@ -55,18 +64,24 @@ class DefaultClockProvider(
}
return if (isClockReactiveVariantsEnabled) {
- val buffer =
- messageBuffers?.infraMessageBuffer ?: LogcatOnlyMessageBuffer(LogLevel.INFO)
- val assets = AssetLoader(ctx, ctx, "clocks/", buffer)
- assets.setSeedColor(settings.seedColor, null)
+ val buffers = messageBuffers ?: ClockMessageBuffers(LogUtil.DEFAULT_MESSAGE_BUFFER)
val fontAxes = ClockFontAxis.merge(FlexClockController.FONT_AXES, settings.axes)
+ val clockSettings = settings.copy(axes = fontAxes.map { it.toSetting() })
+ val typefaceCache =
+ TypefaceCache(buffers.infraMessageBuffer) {
+ // TODO(b/364680873): Move constant to config_clockFontFamily when shipping
+ return@TypefaceCache Typeface.create("google-sans-flex-clock", Typeface.NORMAL)
+ }
FlexClockController(
- ctx,
- resources,
- settings.copy(axes = fontAxes.map { it.toSetting() }),
- assets,
+ ClockContext(
+ ctx,
+ resources,
+ clockSettings,
+ typefaceCache,
+ buffers,
+ buffers.infraMessageBuffer,
+ ),
FLEX_DESIGN,
- messageBuffers,
)
} else {
DefaultClockController(
@@ -139,7 +154,7 @@ class DefaultClockProvider(
alignment =
DigitalAlignment(
HorizontalAlignment.CENTER,
- VerticalAlignment.CENTER,
+ VerticalAlignment.BASELINE,
),
dateTimeFormat = "hh",
),
@@ -158,7 +173,7 @@ class DefaultClockProvider(
alignment =
DigitalAlignment(
HorizontalAlignment.CENTER,
- VerticalAlignment.CENTER,
+ VerticalAlignment.BASELINE,
),
dateTimeFormat = "hh",
),
@@ -177,7 +192,7 @@ class DefaultClockProvider(
alignment =
DigitalAlignment(
HorizontalAlignment.CENTER,
- VerticalAlignment.CENTER,
+ VerticalAlignment.BASELINE,
),
dateTimeFormat = "mm",
),
@@ -196,7 +211,7 @@ class DefaultClockProvider(
alignment =
DigitalAlignment(
HorizontalAlignment.CENTER,
- VerticalAlignment.CENTER,
+ VerticalAlignment.BASELINE,
),
dateTimeFormat = "mm",
),
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
index 6c627e27a19b..c7a3f63e92e7 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
@@ -16,8 +16,6 @@
package com.android.systemui.shared.clocks
-import android.content.Context
-import android.content.res.Resources
import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.AxisType
@@ -26,8 +24,6 @@ import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFontAxis
import com.android.systemui.plugins.clocks.ClockFontAxisSetting
-import com.android.systemui.plugins.clocks.ClockMessageBuffers
-import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
@@ -38,42 +34,28 @@ import java.util.TimeZone
/** Controller for the default flex clock */
class FlexClockController(
- private val ctx: Context,
- private val resources: Resources,
- private val settings: ClockSettings,
- private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources
+ private val clockCtx: ClockContext,
val design: ClockDesign, // TODO(b/364680879): Remove when done inlining
- val messageBuffers: ClockMessageBuffers?,
) : ClockController {
- override val smallClock = run {
- val buffer = messageBuffers?.smallClockMessageBuffer ?: LogUtil.DEFAULT_MESSAGE_BUFFER
+ override val smallClock =
FlexClockFaceController(
- ctx,
- resources,
- assets.copy(messageBuffer = buffer),
+ clockCtx.copy(messageBuffer = clockCtx.messageBuffers.smallClockMessageBuffer),
design.small ?: design.large!!,
- false,
- buffer,
+ isLargeClock = false,
)
- }
- override val largeClock = run {
- val buffer = messageBuffers?.largeClockMessageBuffer ?: LogUtil.DEFAULT_MESSAGE_BUFFER
+ override val largeClock =
FlexClockFaceController(
- ctx,
- resources,
- assets.copy(messageBuffer = buffer),
+ clockCtx.copy(messageBuffer = clockCtx.messageBuffers.largeClockMessageBuffer),
design.large ?: design.small!!,
- true,
- buffer,
+ isLargeClock = true,
)
- }
override val config: ClockConfig by lazy {
ClockConfig(
DEFAULT_CLOCK_ID,
- resources.getString(R.string.clock_default_name),
- resources.getString(R.string.clock_default_description),
+ clockCtx.resources.getString(R.string.clock_default_name),
+ clockCtx.resources.getString(R.string.clock_default_description),
isReactiveToTone = true,
)
}
@@ -125,8 +107,8 @@ class FlexClockController(
}
override fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) {
- val theme = ThemeConfig(isDarkTheme, assets.seedColor)
- events.onFontAxesChanged(settings.axes)
+ val theme = ThemeConfig(isDarkTheme, clockCtx.settings.seedColor)
+ events.onFontAxesChanged(clockCtx.settings.axes)
smallClock.run {
events.onThemeChanged(theme)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
index ee21ea6ee126..a8890e6aa934 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -16,15 +16,12 @@
package com.android.systemui.shared.clocks
-import android.content.Context
-import android.content.res.Resources
import android.graphics.Rect
import android.view.Gravity
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
import com.android.systemui.customization.R
-import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.ClockAnimations
import com.android.systemui.plugins.clocks.ClockEvents
@@ -45,22 +42,19 @@ import kotlin.math.max
// TODO(b/364680879): Merge w/ ComposedDigitalLayerController
class FlexClockFaceController(
- ctx: Context,
- private val resources: Resources,
- val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources
+ clockCtx: ClockContext,
face: ClockFace,
private val isLargeClock: Boolean,
- messageBuffer: MessageBuffer,
) : ClockFaceController {
override val view: View
get() = layerController.view
override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = true)
- override var theme = ThemeConfig(true, assets.seedColor)
+ override var theme = ThemeConfig(true, clockCtx.settings.seedColor)
private val keyguardLargeClockTopMargin =
- resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin)
+ clockCtx.resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin)
val layerController: SimpleClockLayerController
val timespecHandler = DigitalTimespecHandler(DigitalTimespec.TIME_FULL_FORMAT, "hh:mm")
@@ -72,23 +66,10 @@ class FlexClockFaceController(
layerController =
if (isLargeClock) {
- ComposedDigitalLayerController(
- ctx,
- resources,
- assets,
- layer as ComposedDigitalHandLayer,
- messageBuffer,
- )
+ ComposedDigitalLayerController(clockCtx, layer as ComposedDigitalHandLayer)
} else {
- val childView = SimpleDigitalClockTextView(ctx, messageBuffer)
- SimpleDigitalHandLayerController(
- ctx,
- resources,
- assets,
- layer as DigitalHandLayer,
- childView,
- messageBuffer,
- )
+ val childView = SimpleDigitalClockTextView(clockCtx)
+ SimpleDigitalHandLayerController(clockCtx, layer as DigitalHandLayer, childView)
}
layerController.view.layoutParams = lp
}
@@ -97,7 +78,7 @@ class FlexClockFaceController(
private fun offsetGlyphsForStepClockAnimation(
clockStartLeft: Int,
direction: Int,
- fraction: Float
+ fraction: Float,
) {
(view as? FlexClockView)?.offsetGlyphsForStepClockAnimation(
clockStartLeft,
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
index 143b28f1e82b..ebac4b24732b 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
@@ -16,8 +16,6 @@
package com.android.systemui.shared.clocks
-import android.content.Context
-import android.content.res.Resources
import android.graphics.Rect
import android.view.View
import android.view.ViewGroup
@@ -25,7 +23,6 @@ import android.widget.RelativeLayout
import androidx.annotation.VisibleForTesting
import com.android.systemui.customization.R
import com.android.systemui.log.core.Logger
-import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.ClockAnimations
import com.android.systemui.plugins.clocks.ClockEvents
@@ -42,14 +39,11 @@ import java.util.TimeZone
private val TAG = SimpleDigitalHandLayerController::class.simpleName!!
open class SimpleDigitalHandLayerController<T>(
- private val ctx: Context,
- private val resources: Resources,
- private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources
+ private val clockCtx: ClockContext,
private val layer: DigitalHandLayer,
override val view: T,
- messageBuffer: MessageBuffer,
) : SimpleClockLayerController where T : View, T : SimpleDigitalClockView {
- private val logger = Logger(messageBuffer, TAG)
+ private val logger = Logger(clockCtx.messageBuffer, TAG)
val timespec = DigitalTimespecHandler(layer.timespec, layer.dateTimeFormat)
@VisibleForTesting
@@ -75,12 +69,12 @@ open class SimpleDigitalHandLayerController<T>(
layer.alignment.verticalAlignment?.let { view.verticalAlignment = it }
layer.alignment.horizontalAlignment?.let { view.horizontalAlignment = it }
}
- view.applyStyles(assets, layer.style, layer.aodStyle)
+ view.applyStyles(layer.style, layer.aodStyle)
view.id =
- ctx.resources.getIdentifier(
+ clockCtx.resources.getIdentifier(
generateDigitalLayerIdString(layer),
"id",
- ctx.getPackageName(),
+ clockCtx.context.getPackageName(),
)
}
@@ -306,8 +300,9 @@ open class SimpleDigitalHandLayerController<T>(
val color =
when {
theme.seedColor != null -> theme.seedColor!!
- theme.isDarkTheme -> resources.getColor(android.R.color.system_accent1_100)
- else -> resources.getColor(android.R.color.system_accent2_600)
+ theme.isDarkTheme ->
+ clockCtx.resources.getColor(android.R.color.system_accent1_100)
+ else -> clockCtx.resources.getColor(android.R.color.system_accent2_600)
}
view.updateColor(color)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt
index b09332f34e99..d4eb7677c757 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt
@@ -16,24 +16,23 @@
package com.android.systemui.shared.clocks.view
-import android.content.Context
import android.graphics.Canvas
import android.graphics.Point
import android.view.View
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import com.android.systemui.log.core.Logger
-import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
+import com.android.systemui.shared.clocks.ClockContext
import com.android.systemui.shared.clocks.LogUtil
import java.util.Locale
// TODO(b/364680879): Merge w/ only subclass FlexClockView
-abstract class DigitalClockFaceView(ctx: Context, messageBuffer: MessageBuffer) : FrameLayout(ctx) {
- protected val logger = Logger(messageBuffer, this::class.simpleName!!)
+abstract class DigitalClockFaceView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
+ protected val logger = Logger(clockCtx.messageBuffer, this::class.simpleName!!)
get() = field ?: LogUtil.FALLBACK_INIT_LOGGER
abstract var digitalClockTextViewMap: MutableMap<Int, SimpleDigitalClockTextView>
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index 593eba9d05cc..faef18c1e85a 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -16,31 +16,34 @@
package com.android.systemui.shared.clocks.view
-import android.content.Context
import android.graphics.Canvas
import android.graphics.Point
+import android.icu.text.NumberFormat
import android.util.MathUtils.constrainedMap
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
import com.android.app.animation.Interpolators
import com.android.systemui.customization.R
-import com.android.systemui.log.core.MessageBuffer
-import com.android.systemui.shared.clocks.AssetLoader
+import com.android.systemui.shared.clocks.ClockContext
import com.android.systemui.shared.clocks.DigitTranslateAnimator
+import java.util.Locale
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
fun clamp(value: Float, minVal: Float, maxVal: Float): Float = max(min(value, maxVal), minVal)
-class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: MessageBuffer) :
- DigitalClockFaceView(context, messageBuffer) {
+class FlexClockView(clockCtx: ClockContext) : DigitalClockFaceView(clockCtx) {
override var digitalClockTextViewMap = mutableMapOf<Int, SimpleDigitalClockTextView>()
- val digitLeftTopMap = mutableMapOf<Int, Point>()
- var maxSingleDigitSize = Point(-1, -1)
- val lockscreenTranslate = Point(0, 0)
- var aodTranslate = Point(0, 0)
+ private val digitLeftTopMap = mutableMapOf<Int, Point>()
+
+ private var maxSingleDigitSize = Point(-1, -1)
+ private val lockscreenTranslate = Point(0, 0)
+ private var aodTranslate = Point(0, 0)
+
+ // Does the current language have mono vertical size when displaying numerals
+ private var isMonoVerticalNumericLineSpacing = true
init {
setWillNotDraw(false)
@@ -49,6 +52,7 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
)
+ updateLocale(Locale.getDefault())
}
private val digitOffsets = mutableMapOf<Int, Float>()
@@ -61,12 +65,19 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me
protected override fun calculateSize(widthMeasureSpec: Int, heightMeasureSpec: Int): Point {
maxSingleDigitSize = Point(-1, -1)
+ val bottomLocation: (textView: SimpleDigitalClockTextView) -> Int = { textView ->
+ if (isMonoVerticalNumericLineSpacing) {
+ maxSingleDigitSize.y
+ } else {
+ (textView.paint.fontMetrics.descent - textView.paint.fontMetrics.ascent).toInt()
+ }
+ }
+
digitalClockTextViewMap.forEach { (_, textView) ->
textView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
maxSingleDigitSize.x = max(maxSingleDigitSize.x, textView.measuredWidth)
- maxSingleDigitSize.y = max(maxSingleDigitSize.y, textView.measuredHeight)
+ maxSingleDigitSize.y = max(bottomLocation(textView), textView.measuredHeight)
}
- val textView = digitalClockTextViewMap[R.id.HOUR_FIRST_DIGIT]!!
aodTranslate = Point(0, 0)
return Point(
((maxSingleDigitSize.x + abs(aodTranslate.x)) * 2),
@@ -106,6 +117,11 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me
}
}
+ override fun onLocaleChanged(locale: Locale) {
+ updateLocale(locale)
+ requestLayout()
+ }
+
override fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
dozeControlState.animateDoze = {
super.animateDoze(isDozing, isAnimated)
@@ -166,6 +182,18 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me
}
}
+ private fun updateLocale(locale: Locale) {
+ isMonoVerticalNumericLineSpacing =
+ !NON_MONO_VERTICAL_NUMERIC_LINE_SPACING_LANGUAGES.any {
+ val newLocaleNumberFormat =
+ NumberFormat.getInstance(locale).format(FORMAT_NUMBER.toLong())
+ val nonMonoVerticalNumericLineSpaceNumberFormat =
+ NumberFormat.getInstance(Locale.forLanguageTag(it))
+ .format(FORMAT_NUMBER.toLong())
+ newLocaleNumberFormat == nonMonoVerticalNumericLineSpaceNumberFormat
+ }
+ }
+
/**
* Offsets the textViews of the clock for the step clock animation.
*
@@ -264,10 +292,18 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me
// Constants for the animation
private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
+ private const val FORMAT_NUMBER = 1234567890
+
// Total available transition time for each digit, taking into account the step. If step is
// 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
private const val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1)
+ // Add language tags below that do not have vertically mono spaced numerals
+ private val NON_MONO_VERTICAL_NUMERIC_LINE_SPACING_LANGUAGES =
+ setOf(
+ "my", // Burmese
+ )
+
// Use the sign of targetTranslation to control the direction of digit translation
fun updateDirectionalTargetTranslate(id: Int, targetTranslation: Point): Point {
val outPoint = Point(targetTranslation)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index 5c84f2d04ccc..48761c081b9e 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shared.clocks.view
import android.annotation.SuppressLint
-import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
@@ -37,13 +36,11 @@ import android.view.animation.Interpolator
import android.widget.TextView
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.animation.TextAnimator
-import com.android.systemui.animation.TypefaceVariantCache
import com.android.systemui.customization.R
import com.android.systemui.log.core.Logger
-import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.clocks.ClockFontAxisSetting
-import com.android.systemui.shared.clocks.AssetLoader
import com.android.systemui.shared.clocks.ClockAnimation
+import com.android.systemui.shared.clocks.ClockContext
import com.android.systemui.shared.clocks.DigitTranslateAnimator
import com.android.systemui.shared.clocks.DimensionParser
import com.android.systemui.shared.clocks.FontTextStyle
@@ -57,18 +54,15 @@ import kotlin.math.min
private val TAG = SimpleDigitalClockTextView::class.simpleName!!
@SuppressLint("AppCompatCustomView")
-open class SimpleDigitalClockTextView(
- ctx: Context,
- messageBuffer: MessageBuffer,
- attrs: AttributeSet? = null,
-) : TextView(ctx, attrs), SimpleDigitalClockView {
+open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSet? = null) :
+ TextView(clockCtx.context, attrs), SimpleDigitalClockView {
val lockScreenPaint = TextPaint()
override lateinit var textStyle: FontTextStyle
lateinit var aodStyle: FontTextStyle
private var lsFontVariation = ClockFontAxisSetting.toFVar(DEFAULT_LS_VARIATION)
private var aodFontVariation = ClockFontAxisSetting.toFVar(DEFAULT_AOD_VARIATION)
- private val parser = DimensionParser(ctx)
+ private val parser = DimensionParser(clockCtx.context)
var maxSingleDigitHeight = -1
var maxSingleDigitWidth = -1
var digitTranslateAnimator: DigitTranslateAnimator? = null
@@ -91,33 +85,23 @@ open class SimpleDigitalClockTextView(
private val prevTextBounds = Rect()
// targetTextBounds holds the state we are interpolating to
private val targetTextBounds = Rect()
- protected val logger = Logger(messageBuffer, this::class.simpleName!!)
+ protected val logger = Logger(clockCtx.messageBuffer, this::class.simpleName!!)
get() = field ?: LogUtil.FALLBACK_INIT_LOGGER
private var aodDozingInterpolator: Interpolator? = null
@VisibleForTesting lateinit var textAnimator: TextAnimator
- lateinit var typefaceCache: TypefaceVariantCache
- private set
-
- private fun setTypefaceCache(value: TypefaceVariantCache) {
- typefaceCache = value
- if (this::textAnimator.isInitialized) {
- textAnimator.typefaceCache = value
- }
- }
+ private val typefaceCache = clockCtx.typefaceCache.getVariantCache("")
@VisibleForTesting
var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb ->
TextAnimator(layout, ClockAnimation.NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb).also {
- if (this::typefaceCache.isInitialized) {
- it.typefaceCache = typefaceCache
- }
+ it.typefaceCache = typefaceCache
}
}
- override var verticalAlignment: VerticalAlignment = VerticalAlignment.CENTER
+ override var verticalAlignment: VerticalAlignment = VerticalAlignment.BASELINE
override var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.LEFT
override var isAnimationEnabled = true
override var dozeFraction: Float = 0F
@@ -258,7 +242,7 @@ open class SimpleDigitalClockTextView(
-translation.y.toFloat(),
(-translation.x + measuredWidth).toFloat(),
(-translation.y + measuredHeight).toFloat(),
- Paint().also { it.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) },
+ PORTER_DUFF_XFER_MODE_PAINT,
)
canvas.restore()
canvas.restore()
@@ -403,7 +387,7 @@ open class SimpleDigitalClockTextView(
// translation of reference point of text
// used for translation when calling textInterpolator
- fun getLocalTranslation(): Point {
+ private fun getLocalTranslation(): Point {
val viewHeight = if (isVertical) measuredWidth else measuredHeight
val interpolatedTextBounds = updateInterpolatedTextBounds()
val localTranslation = Point(0, 0)
@@ -429,17 +413,18 @@ open class SimpleDigitalClockTextView(
correctedBaseline
}
VerticalAlignment.BASELINE -> {
- localTranslation.y = -lockScreenPaint.strokeWidth.toInt()
+ // account for max bottom distance of font, so clock doesn't collide with elements
+ localTranslation.y =
+ -lockScreenPaint.strokeWidth.toInt() - paint.fontMetrics.descent.toInt()
}
}
return updateXtranslation(localTranslation, interpolatedTextBounds)
}
- override fun applyStyles(assets: AssetLoader, textStyle: TextStyle, aodStyle: TextStyle?) {
+ override fun applyStyles(textStyle: TextStyle, aodStyle: TextStyle?) {
this.textStyle = textStyle as FontTextStyle
val typefaceName = "fonts/" + textStyle.fontFamily
- setTypefaceCache(assets.typefaceCache.getVariantCache(typefaceName))
lockScreenPaint.strokeJoin = Paint.Join.ROUND
lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
textStyle.fontFeatureSettings?.let {
@@ -550,7 +535,9 @@ open class SimpleDigitalClockTextView(
}
companion object {
- val AOD_STROKE_WIDTH = "2dp"
+ private val PORTER_DUFF_XFER_MODE_PAINT =
+ Paint().also { it.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) }
+
val AOD_COLOR = Color.WHITE
val OPTICAL_SIZE_AXIS = ClockFontAxisSetting("opsz", 144f)
val DEFAULT_LS_VARIATION =
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt
index 3d2ed4a1ef40..e8be28fb8df2 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt
@@ -18,7 +18,6 @@ package com.android.systemui.shared.clocks.view
import androidx.annotation.VisibleForTesting
import com.android.systemui.plugins.clocks.ClockFontAxisSetting
-import com.android.systemui.shared.clocks.AssetLoader
import com.android.systemui.shared.clocks.TextStyle
interface SimpleDigitalClockView {
@@ -29,7 +28,7 @@ interface SimpleDigitalClockView {
val textStyle: TextStyle
@VisibleForTesting var isAnimationEnabled: Boolean
- fun applyStyles(assets: AssetLoader, textStyle: TextStyle, aodStyle: TextStyle?)
+ fun applyStyles(textStyle: TextStyle, aodStyle: TextStyle?)
fun applyTextSize(targetFontSizePx: Float?, constrainedByHeight: Boolean = false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
index 57a67973f34f..85bdf9264467 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
@@ -28,7 +28,7 @@ import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.navigationbar.NavigationBarController
import com.android.systemui.settings.FakeDisplayTracker
-import com.android.systemui.shade.data.repository.FakeShadePositionRepository
+import com.android.systemui.shade.data.repository.FakeShadeDisplayRepository
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.util.concurrent.Executor
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -62,7 +62,7 @@ class KeyguardDisplayManagerTest : SysuiTestCase() {
mock(ConnectedDisplayKeyguardPresentation::class.java)
@Mock private val deviceStateHelper = mock(DeviceStateHelper::class.java)
@Mock private val keyguardStateController = mock(KeyguardStateController::class.java)
- private val shadePositionRepository = FakeShadePositionRepository()
+ private val shadePositionRepository = FakeShadeDisplayRepository()
private val mainExecutor = Executor { it.run() }
private val backgroundExecutor = Executor { it.run() }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index 72163e4d7710..f82c8b0e56e0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -31,7 +31,8 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.whenever
@@ -62,7 +63,7 @@ class AuthenticationRepositoryTest : SysuiTestCase() {
private val testScope = kosmos.testScope
private val clock = FakeSystemClock()
private val userRepository = FakeUserRepository()
- private val mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
+ private val mobileConnectionsRepository = kosmos.mobileConnectionsRepository
private lateinit var underTest: AuthenticationRepository
@@ -110,7 +111,7 @@ class AuthenticationRepositoryTest : SysuiTestCase() {
.isEqualTo(AuthenticationMethodModel.None)
currentSecurityMode = KeyguardSecurityModel.SecurityMode.SimPin
- mobileConnectionsRepository.isAnySimSecure.value = true
+ mobileConnectionsRepository.fake.isAnySimSecure.value = true
assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Sim)
assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Sim)
}
@@ -199,7 +200,7 @@ class AuthenticationRepositoryTest : SysuiTestCase() {
}
private fun setSecurityModeAndDispatchBroadcast(
- securityMode: KeyguardSecurityModel.SecurityMode,
+ securityMode: KeyguardSecurityModel.SecurityMode
) {
currentSecurityMode = securityMode
dispatchBroadcast()
@@ -208,23 +209,15 @@ class AuthenticationRepositoryTest : SysuiTestCase() {
private fun dispatchBroadcast() {
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
- Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)
+ Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
)
}
companion object {
private val USER_INFOS =
listOf(
- UserInfo(
- /* id= */ 100,
- /* name= */ "First user",
- /* flags= */ 0,
- ),
- UserInfo(
- /* id= */ 101,
- /* name= */ "Second user",
- /* flags= */ 0,
- ),
+ UserInfo(/* id= */ 100, /* name= */ "First user", /* flags= */ 0),
+ UserInfo(/* id= */ 101, /* name= */ "Second user", /* flags= */ 0),
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
index 566cd70598de..10bf5233e61e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -35,7 +35,8 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -78,7 +79,7 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
+ mobileConnectionsRepository = kosmos.mobileConnectionsRepository.fake
overrideResource(R.string.lockscreen_emergency_call, MESSAGE_EMERGENCY_CALL)
overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
index baeb2dd5aa96..0084e18f519f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
@@ -16,38 +16,64 @@
package com.android.systemui.communal.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val communalSceneRepository = kosmos.fakeCommunalSceneRepository
+ private val sceneInteractor = kosmos.sceneInteractor
private val underTest: CommunalTransitionViewModel by lazy {
kosmos.communalTransitionViewModel
}
+ @DisableFlags(FLAG_SCENE_CONTAINER)
@Test
fun testIsUmoOnCommunalDuringTransitionBetweenLockscreenAndGlanceableHub() =
testScope.runTest {
@@ -61,6 +87,7 @@ class CommunalTransitionViewModelTest : SysuiTestCase() {
assertThat(isUmoOnCommunal).isFalse()
}
+ @DisableFlags(FLAG_SCENE_CONTAINER)
@Test
fun testIsUmoOnCommunalDuringTransitionBetweenDreamingAndGlanceableHub() =
testScope.runTest {
@@ -74,6 +101,7 @@ class CommunalTransitionViewModelTest : SysuiTestCase() {
assertThat(isUmoOnCommunal).isFalse()
}
+ @DisableFlags(FLAG_SCENE_CONTAINER)
@Test
fun testIsUmoOnCommunalDuringTransitionBetweenOccludedAndGlanceableHub() =
testScope.runTest {
@@ -87,6 +115,7 @@ class CommunalTransitionViewModelTest : SysuiTestCase() {
assertThat(isUmoOnCommunal).isFalse()
}
+ @DisableFlags(FLAG_SCENE_CONTAINER)
@Test
fun isUmoOnCommunal_noLongerVisible_returnsFalse() =
testScope.runTest {
@@ -103,6 +132,7 @@ class CommunalTransitionViewModelTest : SysuiTestCase() {
assertThat(isUmoOnCommunal).isFalse()
}
+ @DisableFlags(FLAG_SCENE_CONTAINER)
@Test
fun isUmoOnCommunal_idleOnCommunal_returnsTrue() =
testScope.runTest {
@@ -116,11 +146,25 @@ class CommunalTransitionViewModelTest : SysuiTestCase() {
assertThat(isUmoOnCommunal).isTrue()
}
+ @EnableFlags(FLAG_SCENE_CONTAINER)
+ @Test
+ fun isUmoOnCommunal_sceneContainerEnabled_idleOnCommunal_returnsTrue() =
+ testScope.runTest {
+ val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
+ assertThat(isUmoOnCommunal).isFalse()
+
+ // Change to communal scene.
+ setIdleScene(Scenes.Communal)
+
+ // isUmoOnCommunal returns true, even without any keyguard transition.
+ assertThat(isUmoOnCommunal).isTrue()
+ }
+
private suspend fun TestScope.enterCommunal(from: KeyguardState) {
keyguardTransitionRepository.sendTransitionSteps(
from = from,
to = KeyguardState.GLANCEABLE_HUB,
- testScope
+ testScope,
)
communalSceneRepository.changeScene(CommunalScenes.Communal)
runCurrent()
@@ -130,9 +174,17 @@ class CommunalTransitionViewModelTest : SysuiTestCase() {
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GLANCEABLE_HUB,
to = to,
- testScope
+ testScope,
)
communalSceneRepository.changeScene(CommunalScenes.Blank)
runCurrent()
}
+
+ private fun setIdleScene(scene: SceneKey) {
+ sceneInteractor.changeScene(scene, "test")
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(scene))
+ sceneInteractor.setTransitionState(transitionState)
+ testScope.runCurrent()
+ }
}
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 6b851cb017e1..5bbd3ffc625a 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
@@ -56,6 +56,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInterac
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.media.controls.ui.controller.mediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
@@ -133,6 +134,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
accessibilityManager,
packageManager,
WIDGET_PICKER_PACKAGE_NAME,
+ kosmos.mediaCarouselController,
)
}
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 09daa51a3b37..3eba8ff4b198 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
@@ -69,6 +69,7 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.mediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -178,6 +179,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
mediaHost,
logcatLogBuffer("CommunalViewModelTest"),
metricsLogger,
+ kosmos.mediaCarouselController,
)
}
@@ -627,10 +629,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
kosmos.setTransition(
sceneTransition = Idle(Scenes.Communal),
stateTransition =
- TransitionStep(
- from = KeyguardState.DREAMING,
- to = KeyguardState.GLANCEABLE_HUB
- ),
+ TransitionStep(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB),
)
// Then flow is not frozen
@@ -647,10 +646,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
kosmos.setTransition(
sceneTransition = Idle(Scenes.Lockscreen),
stateTransition =
- TransitionStep(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.OCCLUDED
- ),
+ TransitionStep(from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OCCLUDED),
)
// Then flow is not frozen
@@ -735,10 +731,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
kosmos.setTransition(
sceneTransition = Idle(Scenes.Communal),
stateTransition =
- TransitionStep(
- from = KeyguardState.DREAMING,
- to = KeyguardState.GLANCEABLE_HUB
- ),
+ TransitionStep(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB),
)
// Widgets available
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 8062358f670c..a65e7ed48797 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -19,7 +19,9 @@ import android.app.WindowConfiguration
import android.content.ComponentName
import android.content.Intent
import android.os.RemoteException
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.service.dreams.Flags
import android.service.dreams.IDreamOverlay
import android.service.dreams.IDreamOverlayCallback
@@ -33,7 +35,6 @@ import android.view.WindowManagerImpl
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.app.viewcapture.ViewCapture
import com.android.app.viewcapture.ViewCaptureAwareWindowManager
@@ -43,6 +44,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
@@ -62,12 +64,16 @@ import com.android.systemui.complication.ComplicationLayoutEngine
import com.android.systemui.complication.dagger.ComplicationComponent
import com.android.systemui.dreams.complication.HideComplicationTouchHandler
import com.android.systemui.dreams.dagger.DreamOverlayComponent
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.gesture.domain.gestureInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
import com.android.systemui.navigationbar.gestural.domain.TaskInfo
import com.android.systemui.navigationbar.gestural.domain.TaskMatcher
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.android.systemui.touch.TouchInsetManager
import com.android.systemui.util.concurrency.FakeExecutor
@@ -98,12 +104,14 @@ import org.mockito.kotlin.spy
import org.mockito.kotlin.times
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidJUnit4::class)
-class DreamOverlayServiceTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val mFakeSystemClock = FakeSystemClock()
private val mMainExecutor = FakeExecutor(mFakeSystemClock)
private val kosmos = testKosmos()
@@ -245,6 +253,10 @@ class DreamOverlayServiceTest : SysuiTestCase() {
)
}
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -287,6 +299,7 @@ class DreamOverlayServiceTest : SysuiTestCase() {
mKeyguardUpdateMonitor,
mScrimManager,
mCommunalInteractor,
+ kosmos.sceneInteractor,
mSystemDialogsCloser,
mUiEventLogger,
mTouchInsetManager,
@@ -768,6 +781,7 @@ class DreamOverlayServiceTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
+ @DisableFlags(FLAG_SCENE_CONTAINER)
@kotlin.Throws(RemoteException::class)
fun testTransitionToGlanceableHub() =
testScope.runTest {
@@ -793,6 +807,35 @@ class DreamOverlayServiceTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_SCENE_CONTAINER, FLAG_COMMUNAL_HUB)
+ @kotlin.Throws(RemoteException::class)
+ fun testTransitionToGlanceableHub_sceneContainer() =
+ testScope.runTest {
+ // Inform the overlay service of dream starting. Do not show dream complications.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*isPreview*/,
+ false, /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+
+ verify(mDreamOverlayCallback).onRedirectWake(false)
+ clearInvocations(mDreamOverlayCallback)
+ kosmos.setCommunalAvailable(true)
+ mMainExecutor.runAllReady()
+ runCurrent()
+ verify(mDreamOverlayCallback).onRedirectWake(true)
+ client.onWakeRequested()
+ mMainExecutor.runAllReady()
+ runCurrent()
+ assertThat(kosmos.sceneContainerRepository.currentScene.value)
+ .isEqualTo(Scenes.Communal)
+ verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
@Throws(RemoteException::class)
fun testRedirectExit() =
@@ -911,6 +954,7 @@ class DreamOverlayServiceTest : SysuiTestCase() {
// Verifies that the touch handling lifecycle is STARTED even if the dream starts while not
// focused.
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun testLifecycle_dreamNotFocusedOnStart_isStarted() {
val transitionState: MutableStateFlow<ObservableTransitionState> =
MutableStateFlow(ObservableTransitionState.Idle(CommunalScenes.Blank))
@@ -1024,6 +1068,7 @@ class DreamOverlayServiceTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun testCommunalVisible_setsLifecycleState() {
val client = client
@@ -1060,6 +1105,7 @@ class DreamOverlayServiceTest : SysuiTestCase() {
// Verifies the dream's lifecycle
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun testLifecycleStarted_whenAnyOcclusion() {
val client = client
@@ -1256,5 +1302,11 @@ class DreamOverlayServiceTest : SysuiTestCase() {
ComponentName("package", "homeControlPanel")
private const val DREAM_COMPONENT = "package/dream"
private const val WINDOW_NAME = "test"
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_HUB).andSceneContainer()
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt
index 5efb6171cdde..f6a6e5465e1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt
@@ -119,22 +119,6 @@ class TileHapticsViewModelTest : SysuiTestCase() {
}
@Test
- fun onLongClick_whenTileDoesNotHandleLongClick_playsFailureHaptics() =
- testScope.runTest {
- // WHEN the tile is long-clicked but the tile does not handle a long-click
- val state = QSTile.State().apply { handlesLongClick = false }
- qsTile.changeState(state)
- underTest.setTileInteractionState(
- TileHapticsViewModel.TileInteractionState.LONG_CLICKED
- )
- runCurrent()
-
- // THEN the failure token plays
- assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE)
- assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
- }
-
- @Test
fun whenLaunchingFromClick_doesNotPlayHaptics() =
testScope.runTest {
// WHEN the tile is clicked and its action state changes accordingly
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index 4e429c34bf2a..69fb03dc6433 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -47,7 +47,8 @@ import com.android.systemui.keyguard.data.repository.BiometricType.SIDE_FINGERPR
import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY_FINGERPRINT
import com.android.systemui.keyguard.shared.model.DevicePosture
import com.android.systemui.res.R
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
@@ -99,7 +100,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
private lateinit var devicePostureRepository: FakeDevicePostureRepository
private lateinit var facePropertyRepository: FakeFacePropertyRepository
private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
- private val mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
+ private val mobileConnectionsRepository = kosmos.mobileConnectionsRepository
private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
@@ -142,7 +143,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
1,
SensorStrength.STRONG,
FingerprintSensorType.UDFPS_OPTICAL,
- emptyMap()
+ emptyMap(),
)
verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture())
verify(authController, times(2)).addCallback(authControllerCallback.capture())
@@ -247,7 +248,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
private fun deviceIsInPostureThatSupportsFaceAuth() {
overrideResource(
R.integer.config_face_auth_supported_posture,
- DevicePostureController.DEVICE_POSTURE_FLIPPED
+ DevicePostureController.DEVICE_POSTURE_FLIPPED,
)
devicePostureRepository.setCurrentPosture(DevicePosture.FLIPPED)
}
@@ -459,7 +460,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
biometricsAreEnabledBySettings()
doNotDisableKeyguardAuthFeatures()
- mobileConnectionsRepository.isAnySimSecure.value = false
+ mobileConnectionsRepository.fake.isAnySimSecure.value = false
runCurrent()
val isFaceAuthEnabledAndEnrolled by
@@ -467,7 +468,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
assertThat(isFaceAuthEnabledAndEnrolled).isTrue()
- mobileConnectionsRepository.isAnySimSecure.value = true
+ mobileConnectionsRepository.fake.isAnySimSecure.value = true
runCurrent()
assertThat(isFaceAuthEnabledAndEnrolled).isFalse()
@@ -485,13 +486,13 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
doNotDisableKeyguardAuthFeatures()
faceAuthIsStrongBiometric()
biometricsAreEnabledBySettings()
- mobileConnectionsRepository.isAnySimSecure.value = false
+ mobileConnectionsRepository.fake.isAnySimSecure.value = false
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
onNonStrongAuthChanged(false, PRIMARY_USER_ID)
assertThat(isFaceAuthCurrentlyAllowed).isTrue()
- mobileConnectionsRepository.isAnySimSecure.value = true
+ mobileConnectionsRepository.fake.isAnySimSecure.value = true
assertThat(isFaceAuthCurrentlyAllowed).isFalse()
}
@@ -584,7 +585,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
testScope.runTest {
overrideResource(
R.integer.config_face_auth_supported_posture,
- DevicePostureController.DEVICE_POSTURE_UNKNOWN
+ DevicePostureController.DEVICE_POSTURE_UNKNOWN,
)
createBiometricSettingsRepository()
@@ -597,7 +598,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
testScope.runTest {
overrideResource(
R.integer.config_face_auth_supported_posture,
- DevicePostureController.DEVICE_POSTURE_FLIPPED
+ DevicePostureController.DEVICE_POSTURE_FLIPPED,
)
createBiometricSettingsRepository()
@@ -749,7 +750,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
1,
SensorStrength.STRONG,
FingerprintSensorType.UDFPS_OPTICAL,
- emptyMap()
+ emptyMap(),
)
// Non strong auth is not allowed now, FP is marked strong
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
@@ -761,7 +762,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
1,
SensorStrength.CONVENIENCE,
FingerprintSensorType.UDFPS_OPTICAL,
- emptyMap()
+ emptyMap(),
)
assertThat(isFingerprintCurrentlyAllowed).isFalse()
@@ -769,7 +770,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
1,
SensorStrength.WEAK,
FingerprintSensorType.UDFPS_OPTICAL,
- emptyMap()
+ emptyMap(),
)
assertThat(isFingerprintCurrentlyAllowed).isFalse()
}
@@ -791,7 +792,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
1,
SensorStrength.STRONG,
FingerprintSensorType.UDFPS_OPTICAL,
- emptyMap()
+ emptyMap(),
)
// Non strong auth is not allowed now, FP is marked strong
onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, PRIMARY_USER_ID)
@@ -830,7 +831,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
UserInfo(
/* id= */ PRIMARY_USER_ID,
/* name= */ "primary user",
- /* flags= */ UserInfo.FLAG_PRIMARY
+ /* flags= */ UserInfo.FLAG_PRIMARY,
)
private const val ANOTHER_USER_ID = 1
@@ -838,7 +839,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
UserInfo(
/* id= */ ANOTHER_USER_ID,
/* name= */ "another user",
- /* flags= */ UserInfo.FLAG_PRIMARY
+ /* flags= */ UserInfo.FLAG_PRIMARY,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index b72668bf5be6..921a8a610c37 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -41,8 +41,8 @@ import com.android.systemui.qs.panels.ui.viewmodel.setConfigurationForMediaInRow
import com.android.systemui.res.R
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.android.systemui.util.animation.DisappearParameters
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt
index bf97afed92f4..959081663b56 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt
@@ -27,6 +27,7 @@ import com.android.compose.animation.scene.ObservableTransitionState.Transition.
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -194,6 +195,24 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() {
.isFalse()
}
+ @Test
+ fun invisibleDueToOcclusion_isDreaming_emitsTrue() =
+ testScope.runTest {
+ val invisibleDueToOcclusion by collectLastValue(underTest.invisibleDueToOcclusion)
+
+ // Verify that we start with unoccluded
+ assertWithMessage("Should start unoccluded").that(invisibleDueToOcclusion).isFalse()
+
+ // Start dreaming, which is an occluding activity
+ showOccludingActivity()
+ kosmos.keyguardInteractor.setDreaming(true)
+
+ // Verify not invisible when dreaming
+ assertWithMessage("Should be invisible when dreaming")
+ .that(invisibleDueToOcclusion)
+ .isTrue()
+ }
+
/** Simulates the appearance of a show-when-locked `Activity` in the foreground. */
private fun TestScope.showOccludingActivity() {
keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
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 af0274b1caaa..2e074da02103 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
@@ -73,6 +73,7 @@ import com.android.systemui.keyguard.data.repository.fakeTrustRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.dismissCallbackRegistry
+import com.android.systemui.keyguard.domain.interactor.dozeInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.scenetransition.lockscreenSceneTransitionInteractor
@@ -143,6 +144,8 @@ class SceneContainerStartableTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val deviceEntryHapticsInteractor by lazy { kosmos.deviceEntryHapticsInteractor }
+ private val dozeInteractor by lazy { kosmos.dozeInteractor }
+ private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val sceneBackInteractor by lazy { kosmos.sceneBackInteractor }
private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
@@ -373,6 +376,64 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
@Test
+ fun hydrateVisibility_whileDreaming() =
+ testScope.runTest {
+ val isVisible by collectLastValue(sceneInteractor.isVisible)
+
+ // GIVEN the device is dreaming
+ val transitionState =
+ prepareState(isDeviceUnlocked = false, initialSceneKey = Scenes.Dream)
+ underTest.start()
+ assertThat(isVisible).isFalse()
+ }
+
+ @Test
+ fun hydrateVisibility_onCommunalWhileOccluded() =
+ testScope.runTest {
+ val isVisible by collectLastValue(sceneInteractor.isVisible)
+
+ kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+ true,
+ mock(),
+ )
+ prepareState(isDeviceUnlocked = false, initialSceneKey = Scenes.Communal)
+ underTest.start()
+ runCurrent()
+ assertThat(isVisible).isTrue()
+ }
+
+ @Test
+ fun hydrateVisibility_inCommunalTransition() =
+ testScope.runTest {
+ val isVisible by collectLastValue(sceneInteractor.isVisible)
+
+ // GIVEN the device is dreaming
+ val transitionState =
+ prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = false,
+ initialSceneKey = Scenes.Dream,
+ )
+ underTest.start()
+ assertThat(isVisible).isFalse()
+
+ // WHEN a transition starts to the communal hub
+ sceneInteractor.changeScene(Scenes.Dream, "switching to dream for test")
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.Dream,
+ toScene = Scenes.Communal,
+ currentScene = flowOf(Scenes.Dream),
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ )
+ runCurrent()
+ // THEN scenes are visible
+ assertThat(isVisible).isTrue()
+ }
+
+ @Test
fun startsInLockscreenScene() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -643,7 +704,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
fun switchToAOD_whenAvailable_whenDeviceSleepsLocked() =
testScope.runTest {
kosmos.lockscreenSceneTransitionInteractor.start()
- val asleepState by collectLastValue(kosmos.keyguardInteractor.asleepKeyguardState)
+ val asleepState by collectLastValue(keyguardInteractor.asleepKeyguardState)
val currentTransitionInfo by
collectLastValue(kosmos.keyguardTransitionRepository.currentTransitionInfoInternal)
val transitionState =
@@ -673,7 +734,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
fun switchToDozing_whenAodUnavailable_whenDeviceSleepsLocked() =
testScope.runTest {
kosmos.lockscreenSceneTransitionInteractor.start()
- val asleepState by collectLastValue(kosmos.keyguardInteractor.asleepKeyguardState)
+ val asleepState by collectLastValue(keyguardInteractor.asleepKeyguardState)
val currentTransitionInfo by
collectLastValue(kosmos.keyguardTransitionRepository.currentTransitionInfoInternal)
val transitionState =
@@ -2360,6 +2421,66 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
@Test
+ fun stayOnLockscreen_whenDozingStarted() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ underTest.start()
+
+ // Stay on Lockscreen when dozing and dreaming
+ dozeInteractor.setIsDozing(true)
+ keyguardInteractor.setDreaming(true)
+ kosmos.fakeKeyguardRepository.setDreamingWithOverlay(false)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun switchFromLockscreenToDream_whenDreamStarted() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ underTest.start()
+
+ powerInteractor.setAwakeForTest()
+ keyguardInteractor.setDreaming(true)
+ // Move past initial delay with [KeyguardInteractor#isAbleToDream]
+ advanceTimeBy(600L)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Dream)
+ }
+
+ @Test
+ fun switchFromDreamToLockscreen_whenLockedAndDreamStopped() =
+ testScope.runTest {
+ keyguardInteractor.setDreaming(true)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState(initialSceneKey = Scenes.Dream)
+ assertThat(currentScene).isEqualTo(Scenes.Dream)
+ underTest.start()
+
+ keyguardInteractor.setDreaming(false)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun switchFromDreamToGone_whenUnlockedAndDreamStopped() =
+ testScope.runTest {
+ keyguardInteractor.setDreaming(true)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState(initialSceneKey = Scenes.Dream, isDeviceUnlocked = true)
+ assertThat(currentScene).isEqualTo(Scenes.Dream)
+ underTest.start()
+
+ keyguardInteractor.setDreaming(false)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
fun replacesLockscreenSceneOnBackStack_whenUnlockdViaAlternateBouncer_fromShade() =
testScope.runTest {
val transitionState =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 01c17bd6285c..94a19c80dc00 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -148,7 +148,6 @@ import com.android.systemui.statusbar.QsFrameTranslateController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
@@ -428,7 +427,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
mKosmos.getDeviceProvisioningInteractor(),
- new FakeDisableFlagsRepository(),
+ mKosmos.getDisableFlagsInteractor(),
mDozeParameters,
mFakeKeyguardRepository,
mKeyguardTransitionInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 443595dbdf77..ef132d5a4989 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -149,7 +149,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
@Mock protected LargeScreenHeaderHelper mLargeScreenHeaderHelper;
protected FakeDisableFlagsRepository mDisableFlagsRepository =
- new FakeDisableFlagsRepository();
+ mKosmos.getFakeDisableFlagsRepository();
protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
protected FakeShadeRepository mShadeRepository = new FakeShadeRepository();
@@ -185,7 +185,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
mKosmos.getDeviceProvisioningInteractor(),
- mDisableFlagsRepository,
+ mKosmos.getDisableFlagsInteractor(),
mDozeParameters,
mKeyguardRepository,
keyguardTransitionInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
index 46961d4db0f6..ee3f8016c410 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
@@ -19,7 +19,7 @@ package com.android.systemui.shade
import android.app.StatusBarManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelChildren
@@ -61,7 +61,7 @@ class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImp
mDisableFlagsRepository.disableFlags.value =
DisableFlagsModel(
StatusBarManager.DISABLE_NONE,
- StatusBarManager.DISABLE2_QUICK_SETTINGS
+ StatusBarManager.DISABLE2_QUICK_SETTINGS,
)
runCurrent()
@@ -76,7 +76,7 @@ class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImp
mDisableFlagsRepository.disableFlags.value =
DisableFlagsModel(
StatusBarManager.DISABLE_NONE,
- StatusBarManager.DISABLE2_NOTIFICATION_SHADE
+ StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
index a9a5cac6112e..4e7839efe2a3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
@@ -34,13 +34,13 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
-class ShadePositionRepositoryTest : SysuiTestCase() {
+class ShadeDisplaysRepositoryTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val commandRegistry = kosmos.commandRegistry
private val pw = PrintWriter(StringWriter())
- private val underTest = ShadePositionRepositoryImpl(commandRegistry)
+ private val underTest = ShadeDisplaysRepositoryImpl(commandRegistry)
@Before
fun setUp() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
index f192b59cb9e4..8ef1e568cd58 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
@@ -28,7 +28,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.FakeDisplayWindowPropertiesRepository
import com.android.systemui.display.shared.model.DisplayWindowProperties
import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.data.repository.FakeShadePositionRepository
+import com.android.systemui.shade.data.repository.FakeShadeDisplayRepository
import com.android.systemui.statusbar.phone.ConfigurationForwarder
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -50,7 +50,7 @@ import org.mockito.kotlin.whenever
class ShadeDisplaysInteractorTest : SysuiTestCase() {
private val shadeRootview = mock<WindowRootView>()
- private val positionRepository = FakeShadePositionRepository()
+ private val positionRepository = FakeShadeDisplayRepository()
private val defaultContext = mock<Context>()
private val secondaryContext = mock<Context>()
private val contextStore = FakeDisplayWindowPropertiesRepository()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index 19ac0cf40160..da652c4e1b5e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -37,8 +37,8 @@ import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.shade.shadeTestUtil
-import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
import com.android.systemui.statusbar.phone.dozeParameters
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
index 907c68440b55..cd078211f934 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
@@ -32,7 +32,7 @@ import com.android.systemui.log.LogBufferFactory
import com.android.systemui.res.R
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
-import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
index 79ff4be253e1..7eac7e86e372 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
@@ -21,8 +21,8 @@ import com.android.systemui.SysUITestComponent
import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
@@ -51,10 +51,7 @@ class NotificationAlertsInteractorTest : SysuiTestCase() {
fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() =
with(testComponent) {
disableFlags.disableFlags.value =
- DisableFlagsModel(
- StatusBarManager.DISABLE_NONE,
- StatusBarManager.DISABLE2_NONE,
- )
+ DisableFlagsModel(StatusBarManager.DISABLE_NONE, StatusBarManager.DISABLE2_NONE)
assertThat(underTest.areNotificationAlertsEnabled()).isTrue()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index f6d439ab2639..5a77f3d40e82 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.os.ParcelUuid
-import android.telephony.SubscriptionManager
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
@@ -25,91 +24,78 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.flags.fake
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryLogbufferName
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.data.repository.fake
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.CarrierConfigTracker
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.util.UUID
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
-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.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class MobileIconsInteractorTest : SysuiTestCase() {
- private val kosmos = testKosmos()
-
- private lateinit var underTest: MobileIconsInteractor
- private lateinit var connectivityRepository: FakeConnectivityRepository
- private lateinit var connectionsRepository: FakeMobileConnectionsRepository
- private val userSetupRepository = FakeUserSetupRepository()
- private val mobileMappingsProxy = FakeMobileMappingsProxy()
- private val flags =
- FakeFeatureFlagsClassic().apply {
- set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
+ private val kosmos by lazy {
+ testKosmos().apply {
+ mobileConnectionsRepositoryLogbufferName = "MobileIconsInteractorTest"
+ mobileConnectionsRepository.fake.run {
+ setMobileConnectionRepositoryMap(
+ mapOf(
+ SUB_1_ID to CONNECTION_1,
+ SUB_2_ID to CONNECTION_2,
+ SUB_3_ID to CONNECTION_3,
+ SUB_4_ID to CONNECTION_4,
+ )
+ )
+ setActiveMobileDataSubscriptionId(SUB_1_ID)
+ }
+ featureFlagsClassic.fake.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
}
+ }
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
-
- private val tableLogBuffer = logcatTableLogBuffer(kosmos, "MobileIconsInteractorTest")
-
- @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- connectivityRepository = FakeConnectivityRepository()
-
- connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer)
- connectionsRepository.setMobileConnectionRepositoryMap(
- mapOf(
- SUB_1_ID to CONNECTION_1,
- SUB_2_ID to CONNECTION_2,
- SUB_3_ID to CONNECTION_3,
- SUB_4_ID to CONNECTION_4,
- )
+ // shortcut rename
+ private val Kosmos.connectionsRepository by Fixture { mobileConnectionsRepository.fake }
+
+ private val Kosmos.carrierConfigTracker by Fixture { mock<CarrierConfigTracker>() }
+
+ private val Kosmos.underTest by Fixture {
+ MobileIconsInteractorImpl(
+ mobileConnectionsRepository,
+ carrierConfigTracker,
+ tableLogger = mock(),
+ connectivityRepository,
+ FakeUserSetupRepository(),
+ testScope.backgroundScope,
+ context,
+ featureFlagsClassic,
)
- connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
-
- underTest =
- MobileIconsInteractorImpl(
- connectionsRepository,
- carrierConfigTracker,
- tableLogger = mock(),
- connectivityRepository,
- userSetupRepository,
- testScope.backgroundScope,
- context,
- flags,
- )
}
@Test
fun filteredSubscriptions_default() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf<SubscriptionModel>())
@@ -118,7 +104,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
// Based on the logic from the old pipeline, we'll never filter subs when there are more than 2
@Test
fun filteredSubscriptions_moreThanTwo_doesNotFilter() =
- testScope.runTest {
+ kosmos.runTest {
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
@@ -129,7 +115,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
- testScope.runTest {
+ kosmos.runTest {
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
val latest by collectLastValue(underTest.filteredSubscriptions)
@@ -139,7 +125,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_opportunistic_differentGroups_doesNotFilter() =
- testScope.runTest {
+ kosmos.runTest {
connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
@@ -150,7 +136,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_opportunistic_nonGrouped_doesNotFilter() =
- testScope.runTest {
+ kosmos.runTest {
val (sub1, sub2) =
createSubscriptionPair(
subscriptionIds = Pair(SUB_1_ID, SUB_2_ID),
@@ -167,7 +153,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_3() =
- testScope.runTest {
+ kosmos.runTest {
val (sub3, sub4) =
createSubscriptionPair(
subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
@@ -187,7 +173,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_4() =
- testScope.runTest {
+ kosmos.runTest {
val (sub3, sub4) =
createSubscriptionPair(
subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
@@ -207,7 +193,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_active_1() =
- testScope.runTest {
+ kosmos.runTest {
val (sub1, sub3) =
createSubscriptionPair(
subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
@@ -228,7 +214,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_nonActive_1() =
- testScope.runTest {
+ kosmos.runTest {
val (sub1, sub3) =
createSubscriptionPair(
subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
@@ -249,7 +235,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_vcnSubId_agreesWithActiveSubId_usesActiveAkaVcnSub() =
- testScope.runTest {
+ kosmos.runTest {
val (sub1, sub3) =
createSubscriptionPair(
subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
@@ -258,7 +244,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
)
connectionsRepository.setSubscriptions(listOf(sub1, sub3))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
- connectivityRepository.vcnSubId.value = SUB_3_ID
+ kosmos.connectivityRepository.fake.vcnSubId.value = SUB_3_ID
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
@@ -269,7 +255,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_vcnSubId_disagreesWithActiveSubId_usesVcnSub() =
- testScope.runTest {
+ kosmos.runTest {
val (sub1, sub3) =
createSubscriptionPair(
subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
@@ -278,7 +264,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
)
connectionsRepository.setSubscriptions(listOf(sub1, sub3))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
- connectivityRepository.vcnSubId.value = SUB_1_ID
+ kosmos.connectivityRepository.fake.vcnSubId.value = SUB_1_ID
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
@@ -289,9 +275,9 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_doesNotFilterProvisioningWhenFlagIsFalse() =
- testScope.runTest {
+ kosmos.runTest {
// GIVEN the flag is false
- flags.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, false)
+ featureFlagsClassic.fake.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, false)
// GIVEN 1 sub that is in PROFILE_CLASS_PROVISIONING
val sub1 =
@@ -313,7 +299,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_filtersOutProvisioningSubs() =
- testScope.runTest {
+ kosmos.runTest {
val sub1 =
SubscriptionModel(
subscriptionId = SUB_1_ID,
@@ -326,7 +312,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
subscriptionId = SUB_2_ID,
isOpportunistic = false,
carrierName = "Carrier 2",
- profileClass = SubscriptionManager.PROFILE_CLASS_PROVISIONING,
+ profileClass = PROFILE_CLASS_PROVISIONING,
)
connectionsRepository.setSubscriptions(listOf(sub1, sub2))
@@ -339,7 +325,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
/** Note: I'm not sure if this will ever be the case, but we can test it at least */
@Test
fun filteredSubscriptions_filtersOutProvisioningSubsBeforeOpportunistic() =
- testScope.runTest {
+ kosmos.runTest {
// This is a contrived test case, where the active subId is the one that would
// also be filtered by opportunistic filtering.
@@ -376,7 +362,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_groupedPairAndNonProvisioned_groupedFilteringStillHappens() =
- testScope.runTest {
+ kosmos.runTest {
// Grouped filtering only happens when the list of subs is length 2. In this case
// we'll show that filtering of provisioning subs happens before, and thus grouped
// filtering happens even though the unfiltered list is length 3
@@ -406,7 +392,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() =
- testScope.runTest {
+ kosmos.runTest {
val notExclusivelyNonTerrestrialSub =
SubscriptionModel(
isExclusivelyNonTerrestrial = false,
@@ -424,7 +410,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() =
- testScope.runTest {
+ kosmos.runTest {
val exclusivelyNonTerrestrialSub =
SubscriptionModel(
isExclusivelyNonTerrestrial = true,
@@ -442,7 +428,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscription_mixOfExclusivelyNonTerrestrialAndOther_hasOtherSubsOnly() =
- testScope.runTest {
+ kosmos.runTest {
val exclusivelyNonTerrestrialSub =
SubscriptionModel(
isExclusivelyNonTerrestrial = true,
@@ -476,7 +462,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_exclusivelyNonTerrestrialSub_andOpportunistic_bothFiltersHappen() =
- testScope.runTest {
+ kosmos.runTest {
// Exclusively non-terrestrial sub
val exclusivelyNonTerrestrialSub =
SubscriptionModel(
@@ -507,7 +493,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun activeDataConnection_turnedOn() =
- testScope.runTest {
+ kosmos.runTest {
CONNECTION_1.setDataEnabled(true)
val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
@@ -517,7 +503,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun activeDataConnection_turnedOff() =
- testScope.runTest {
+ kosmos.runTest {
CONNECTION_1.setDataEnabled(true)
val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
@@ -528,7 +514,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun activeDataConnection_invalidSubId() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID)
@@ -539,7 +525,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun failedConnection_default_validated_notFailed() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
@@ -550,7 +536,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun failedConnection_notDefault_notValidated_notFailed() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = false
@@ -561,7 +547,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun failedConnection_default_notValidated_failed() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
@@ -572,7 +558,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun failedConnection_carrierMergedDefault_notValidated_failed() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.hasCarrierMergedConnection.value = true
@@ -584,7 +570,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
/** Regression test for b/275076959. */
@Test
fun failedConnection_dataSwitchInSameGroup_notFailed() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
@@ -602,7 +588,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun failedConnection_dataSwitchNotInSameGroup_isFailed() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
@@ -618,7 +604,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun alwaysShowDataRatIcon_configHasTrue() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
val config = MobileMappings.Config()
@@ -630,7 +616,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun alwaysShowDataRatIcon_configHasFalse() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
val config = MobileMappings.Config()
@@ -642,7 +628,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun alwaysUseCdmaLevel_configHasTrue() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
val config = MobileMappings.Config()
@@ -654,7 +640,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun alwaysUseCdmaLevel_configHasFalse() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
val config = MobileMappings.Config()
@@ -666,7 +652,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun isSingleCarrier_zeroSubscriptions_false() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isSingleCarrier)
connectionsRepository.setSubscriptions(emptyList())
@@ -676,7 +662,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun isSingleCarrier_oneSubscription_true() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isSingleCarrier)
connectionsRepository.setSubscriptions(listOf(SUB_1))
@@ -686,7 +672,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun isSingleCarrier_twoSubscriptions_false() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isSingleCarrier)
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
@@ -696,7 +682,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun isSingleCarrier_updates() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isSingleCarrier)
connectionsRepository.setSubscriptions(listOf(SUB_1))
@@ -708,7 +694,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.mobileIsDefault)
connectionsRepository.mobileIsDefault.value = false
@@ -719,7 +705,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.mobileIsDefault)
connectionsRepository.mobileIsDefault.value = true
@@ -731,7 +717,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
/** Regression test for b/272586234. */
@Test
fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.mobileIsDefault)
connectionsRepository.mobileIsDefault.value = false
@@ -742,7 +728,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun mobileIsDefault_updatesWhenRepoUpdates() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.mobileIsDefault)
connectionsRepository.mobileIsDefault.value = true
@@ -760,7 +746,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun dataSwitch_inSameGroup_validatedMatchesPreviousValue_expiresAfter2s() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
@@ -774,17 +760,17 @@ class MobileIconsInteractorTest : SysuiTestCase() {
// After 1s, the force validation bit is still present, so the connection is not marked
// as failed
- advanceTimeBy(1000)
+ testScope.advanceTimeBy(1000)
assertThat(latest).isFalse()
// After 2s, the force validation expires so the connection updates to failed
- advanceTimeBy(1001)
+ testScope.advanceTimeBy(1001)
assertThat(latest).isTrue()
}
@Test
fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
@@ -798,7 +784,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isDefaultConnectionFailed)
// GIVEN the network starts validated
@@ -819,17 +805,17 @@ class MobileIconsInteractorTest : SysuiTestCase() {
// THEN the forced validation bit is still used...
assertThat(latest).isFalse()
- advanceTimeBy(1000)
+ testScope.advanceTimeBy(1000)
assertThat(latest).isFalse()
// ... but expires after 2s
- advanceTimeBy(1001)
+ testScope.advanceTimeBy(1001)
assertThat(latest).isTrue()
}
@Test
fun dataSwitch_whileAlreadyForcingValidation_resetsClock() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
@@ -837,7 +823,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
- advanceTimeBy(1000)
+ testScope.advanceTimeBy(1000)
// WHEN another change in same group event happens
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
@@ -847,37 +833,37 @@ class MobileIconsInteractorTest : SysuiTestCase() {
// THEN the forced validation remains for exactly 2 more seconds from now
// 1.500s from second event
- advanceTimeBy(1500)
+ testScope.advanceTimeBy(1500)
assertThat(latest).isFalse()
// 2.001s from the second event
- advanceTimeBy(501)
+ testScope.advanceTimeBy(501)
assertThat(latest).isTrue()
}
@Test
fun isForceHidden_repoHasMobileHidden_true() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isForceHidden)
- connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
+ kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
assertThat(latest).isTrue()
}
@Test
fun isForceHidden_repoDoesNotHaveMobileHidden_false() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isForceHidden)
- connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
+ kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
assertThat(latest).isFalse()
}
@Test
fun iconInteractor_cachedPerSubId() =
- testScope.runTest {
+ kosmos.runTest {
val interactor1 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
val interactor2 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
@@ -887,7 +873,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isDeviceInEmergencyCallsOnlyMode)
connectionsRepository.isDeviceEmergencyCallCapable.value = true
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt
index 46f822aae7b8..db24d4bc8070 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt
@@ -21,18 +21,18 @@ import android.app.StatusBarManager.DISABLE_CLOCK
import android.app.StatusBarManager.DISABLE_NONE
import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
-import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
-import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
-import org.junit.runner.RunWith;
+import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index c4d2569bba89..b9cdd06de5a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -58,8 +58,8 @@ import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsVie
import com.android.systemui.statusbar.data.model.StatusBarMode
import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository.Companion.DISPLAY_ID
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
-import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
import com.android.systemui.statusbar.events.data.repository.systemStatusEventAnimationRepository
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingIn
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingOut
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index d823bf57c824..4eef308a2772 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -27,10 +27,14 @@ import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
@@ -40,9 +44,7 @@ import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkMode
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.time.fakeSystemClock
import com.android.wifitrackerlib.HotspotNetworkEntry
import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
@@ -53,31 +55,23 @@ import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
import com.android.wifitrackerlib.WifiPickerTracker
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.kotlin.any
-import org.mockito.kotlin.capture
+import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
-import org.mockito.kotlin.times
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-/**
- * Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests.
- *
- * Any new tests added here may also need to be added to [WifiRepositoryImplTest].
- */
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class WifiRepositoryImplTest() : SysuiTestCase() {
- private val kosmos = testKosmos()
+class WifiRepositoryImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val userRepository = kosmos.fakeUserRepository
// Using lazy means that the class will only be constructed once it's fetched. Because the
@@ -108,13 +102,13 @@ class WifiRepositoryImplTest() : SysuiTestCase() {
private val callbackCaptor = argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
- private val dispatcher = StandardTestDispatcher()
- private val testScope = TestScope(dispatcher)
+ private val dispatcher = kosmos.testDispatcher
+ private val testScope = kosmos.testScope
@Before
fun setUp() {
userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
- whenever(wifiPickerTrackerFactory.create(any(), any(), capture(callbackCaptor), any()))
+ whenever(wifiPickerTrackerFactory.create(any(), any(), callbackCaptor.capture(), any()))
.thenReturn(wifiPickerTracker)
}
@@ -122,7 +116,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() {
fun wifiPickerTrackerCreation_scansDisabled() =
testScope.runTest {
collectLastValue(underTest.wifiNetwork)
- testScope.runCurrent()
verify(wifiPickerTracker).disableScanning()
}
@@ -1001,7 +994,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() {
mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(false) }
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
}
@@ -1017,7 +1009,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() {
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
}
@@ -1034,7 +1025,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() {
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
}
@@ -1051,7 +1041,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() {
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
}
@@ -1068,7 +1057,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() {
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
}
@@ -1085,7 +1073,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() {
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
}
@@ -1103,14 +1090,12 @@ class WifiRepositoryImplTest() : SysuiTestCase() {
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
// WHEN the network is lost
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
// THEN the isWifiConnected updates
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
@@ -1216,9 +1201,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() {
assertThat(latest).isEmpty()
}
- // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
- // this needs to
- // be updated to capture the argument instead so currentUserContext can be private.
@Test
@EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
fun oneUserVerifyCreatingWifiPickerTracker_multiuserFlagEnabled() =
@@ -1230,16 +1212,16 @@ class WifiRepositoryImplTest() : SysuiTestCase() {
)
userRepository.setSelectedUserInfo(PRIMARY_USER)
- runCurrent()
- val currentUserContext by collectLastValue(underTest.selectedUserContext)
- assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
- verify(wifiPickerTrackerFactory).create(any(), any(), any(), any())
+ collectLastValue(underTest.wifiNetwork)
+
+ val contextCaptor = argumentCaptor<Context>()
+ verify(wifiPickerTrackerFactory).create(contextCaptor.capture(), any(), any(), any())
+ // If the flag is on, verify that we use the context from #createContextAsUser and we
+ // do NOT use the top-level context
+ assertThat(contextCaptor.firstValue).isEqualTo(primaryUserMockContext)
}
- // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
- // this needs to
- // be updated to capture the argument instead so currentUserContext can be private.
@Test
@EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
fun changeUserVerifyCreatingWifiPickerTracker_multiuserEnabled() =
@@ -1249,32 +1231,30 @@ class WifiRepositoryImplTest() : SysuiTestCase() {
UserHandle.of(PRIMARY_USER_ID),
primaryUserMockContext,
)
-
- runCurrent()
userRepository.setSelectedUserInfo(PRIMARY_USER)
- runCurrent()
- val currentUserContext by collectLastValue(underTest.selectedUserContext)
- assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+ collectLastValue(underTest.wifiNetwork)
+
+ val contextCaptor = argumentCaptor<Context>()
+ verify(wifiPickerTrackerFactory).create(contextCaptor.capture(), any(), any(), any())
+ assertThat(contextCaptor.firstValue).isEqualTo(primaryUserMockContext)
+ reset(wifiPickerTrackerFactory)
+
+ // WHEN we switch to a different user
val otherUserMockContext = mock<Context>()
mContext.prepareCreateContextAsUser(
UserHandle.of(ANOTHER_USER_ID),
otherUserMockContext,
)
-
- runCurrent()
userRepository.setSelectedUserInfo(ANOTHER_USER)
- runCurrent()
- val otherUserContext by collectLastValue(underTest.selectedUserContext)
- assertThat(otherUserContext).isEqualTo(otherUserMockContext)
- verify(wifiPickerTrackerFactory, times(2)).create(any(), any(), any(), any())
+ // THEN we use the different user's context to create WifiPickerTracker
+ val newCaptor = argumentCaptor<Context>()
+ verify(wifiPickerTrackerFactory).create(newCaptor.capture(), any(), any(), any())
+ assertThat(newCaptor.firstValue).isEqualTo(otherUserMockContext)
}
- // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
- // this needs to
- // be updated to capture the argument instead so currentUserContext can be private.
@Test
@DisableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
fun changeUserVerifyCreatingWifiPickerTracker_multiuserDisabled() =
@@ -1285,36 +1265,39 @@ class WifiRepositoryImplTest() : SysuiTestCase() {
primaryUserMockContext,
)
- runCurrent()
userRepository.setSelectedUserInfo(PRIMARY_USER)
- runCurrent()
- val currentUserContext by collectLastValue(underTest.selectedUserContext)
- assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+ collectLastValue(underTest.wifiNetwork)
+
+ val contextCaptor = argumentCaptor<Context>()
+ verify(wifiPickerTrackerFactory).create(contextCaptor.capture(), any(), any(), any())
+ // If the flag is off, verify that we do NOT use the context from #createContextAsUser
+ // and we instead use the top-level context
+ assertThat(contextCaptor.firstValue).isNotEqualTo(primaryUserMockContext)
+ assertThat(contextCaptor.firstValue).isEqualTo(mContext)
+
+ reset(wifiPickerTrackerFactory)
+ // WHEN we switch to a different user
val otherUserMockContext = mock<Context>()
mContext.prepareCreateContextAsUser(
UserHandle.of(ANOTHER_USER_ID),
otherUserMockContext,
)
-
- runCurrent()
userRepository.setSelectedUserInfo(ANOTHER_USER)
- runCurrent()
- verify(wifiPickerTrackerFactory, times(1)).create(any(), any(), any(), any())
+ // THEN we do NOT re-create WifiPickerTracker because the multiuser flag is off
+ verify(wifiPickerTrackerFactory, never()).create(any(), any(), any(), any())
}
private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback {
- testScope.runCurrent()
- return callbackCaptor.value
+ return callbackCaptor.firstValue
}
private fun getTrafficStateCallback(): WifiManager.TrafficStateCallback {
- testScope.runCurrent()
val callbackCaptor = argumentCaptor<WifiManager.TrafficStateCallback>()
verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
+ return callbackCaptor.firstValue
}
private fun createHotspotWithType(@DeviceType type: Int): HotspotNetworkEntry {
@@ -1325,10 +1308,9 @@ class WifiRepositoryImplTest() : SysuiTestCase() {
}
private fun getScanResultsCallback(): WifiManager.ScanResultsCallback {
- testScope.runCurrent()
val callbackCaptor = argumentCaptor<WifiManager.ScanResultsCallback>()
verify(wifiManager).registerScanResultsCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
+ return callbackCaptor.firstValue
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index 0e32c9526df2..e686edec8514 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -24,6 +24,7 @@ import android.content.applicationContext
import android.provider.Settings
import android.service.notification.SystemZenRules
import android.service.notification.ZenModeConfig
+import android.service.notification.ZenModeConfig.ScheduleInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.TestModeBuilder
@@ -65,6 +66,7 @@ class ModesDialogViewModelTest : SysuiTestCase() {
private lateinit var underTest: ModesDialogViewModel
private lateinit var timeScheduleMode: ZenMode
+ private lateinit var timeScheduleInfo: ScheduleInfo
@Before
fun setUp() {
@@ -78,18 +80,18 @@ class ModesDialogViewModelTest : SysuiTestCase() {
kosmos.mockModesDialogEventLogger,
)
- val scheduleInfo = ZenModeConfig.ScheduleInfo()
- scheduleInfo.days = intArrayOf(Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY)
- scheduleInfo.startHour = 11
- scheduleInfo.endHour = 15
+ timeScheduleInfo = ZenModeConfig.ScheduleInfo()
+ timeScheduleInfo.days = intArrayOf(Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY)
+ timeScheduleInfo.startHour = 11
+ timeScheduleInfo.endHour = 15
timeScheduleMode =
TestModeBuilder()
.setPackage(SystemZenRules.PACKAGE_ANDROID)
.setType(AutomaticZenRule.TYPE_SCHEDULE_TIME)
.setManualInvocationAllowed(true)
- .setConditionId(ZenModeConfig.toScheduleConditionId(scheduleInfo))
+ .setConditionId(ZenModeConfig.toScheduleConditionId(timeScheduleInfo))
.setTriggerDescription(
- SystemZenRules.getTriggerDescriptionForScheduleTime(mContext, scheduleInfo)
+ SystemZenRules.getTriggerDescriptionForScheduleTime(mContext, timeScheduleInfo)
)
.build()
}
@@ -358,7 +360,7 @@ class ModesDialogViewModelTest : SysuiTestCase() {
assertThat(tiles!![3].subtext).isEqualTo("Off")
assertThat(tiles!![4].subtext).isEqualTo("On")
assertThat(tiles!![5].subtext).isEqualTo("Not set")
- assertThat(tiles!![6].subtext).isEqualTo("Mon - Wed, 11:00 AM - 3:00 PM")
+ assertThat(tiles!![6].subtext).isEqualTo(timeScheduleMode.triggerDescription)
}
@Test
@@ -437,7 +439,8 @@ class ModesDialogViewModelTest : SysuiTestCase() {
with(tiles?.elementAt(6)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
assertThat(this.subtextDescription)
- .isEqualTo("Monday to Wednesday, 11:00 AM - 3:00 PM")
+ .isEqualTo(SystemZenRules.getDaysOfWeekFull(context, timeScheduleInfo)
+ + ", " + SystemZenRules.getTimeSummary(context, timeScheduleInfo))
}
// All tiles have the same long click info
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
new file mode 100644
index 000000000000..7a9d0179b239
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.window
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val fakeDisplayRepository = kosmos.displayRepository
+ private val testScope = kosmos.testScope
+
+ private val underTest by lazy { kosmos.multiDisplayStatusBarWindowControllerStore }
+
+ @Before
+ fun start() {
+ underTest.start()
+ }
+
+ @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+ @Test
+ fun beforeDisplayRemoved_doesNotStopInstances() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ verify(instance, never()).stop()
+ }
+
+ @Test
+ fun displayRemoved_stopsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).stop()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
index 6e66287c1683..61c719319de8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
@@ -17,12 +17,17 @@ package com.android.systemui.statusbar.window
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.view.fakeWindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.fragments.fragmentService
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.policy.statusBarConfigurationController
import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
@@ -37,6 +42,9 @@ class StatusBarWindowControllerImplTest : SysuiTestCase() {
testKosmos().also { it.statusBarWindowViewInflater = it.fakeStatusBarWindowViewInflater }
private val underTest = kosmos.statusBarWindowControllerImpl
+ private val fakeExecutor = kosmos.fakeExecutor
+ private val fakeWindowManager = kosmos.fakeWindowManager
+ private val mockFragmentService = kosmos.fragmentService
private val fakeStatusBarWindowViewInflater = kosmos.fakeStatusBarWindowViewInflater
private val statusBarConfigurationController = kosmos.statusBarConfigurationController
@@ -59,4 +67,64 @@ class StatusBarWindowControllerImplTest : SysuiTestCase() {
verify(mockWindowView, never()).setStatusBarConfigurationController(any())
}
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarConnectedDisplays.FLAG_NAME)
+ fun stop_statusBarModernizationFlagEnabled_doesNotRemoveFragment() {
+ val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first()
+
+ underTest.stop()
+ fakeExecutor.runAllReady()
+
+ verify(mockFragmentService, never()).removeAndDestroy(windowView)
+ }
+
+ @Test
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun stop_statusBarModernizationFlagDisabled_removesFragment() {
+ val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first()
+
+ underTest.stop()
+ fakeExecutor.runAllReady()
+
+ verify(mockFragmentService).removeAndDestroy(windowView)
+ }
+
+ @Test
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun stop_statusBarModernizationFlagDisabled_removesFragmentOnExecutor() {
+ val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first()
+
+ underTest.stop()
+
+ verify(mockFragmentService, never()).removeAndDestroy(windowView)
+ fakeExecutor.runAllReady()
+ verify(mockFragmentService).removeAndDestroy(windowView)
+ }
+
+ @Test
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun stop_removesWindowViewFromWindowManager() {
+ underTest.attach()
+ underTest.stop()
+
+ assertThat(fakeWindowManager.addedViews).isEmpty()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun stop_connectedDisplaysFlagDisabled_crashes() {
+ underTest.stop()
+ }
+
+ @Test
+ fun attach_windowViewAddedToWindowManager() {
+ val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first()
+
+ underTest.attach()
+
+ assertThat(fakeWindowManager.addedViews.keys).containsExactly(windowView)
+ }
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 89da46544f1e..fb9e96c820cf 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -139,7 +139,9 @@ data class ClockMessageBuffers(
/** Message buffer for large clock rendering */
val largeClockMessageBuffer: MessageBuffer,
-)
+) {
+ constructor(buffer: MessageBuffer) : this(buffer, buffer, buffer) {}
+}
data class AodClockBurnInModel(val scale: Float, val translationX: Float, val translationY: Float)
diff --git a/packages/SystemUI/res/drawable/volume_background_top.xml b/packages/SystemUI/res/drawable/volume_background_top.xml
index 75849beeb016..132572a41a36 100644
--- a/packages/SystemUI/res/drawable/volume_background_top.xml
+++ b/packages/SystemUI/res/drawable/volume_background_top.xml
@@ -17,7 +17,6 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<item>
<shape>
- <size android:width="@dimen/volume_dialog_width" />
<solid android:color="?androidprv:attr/materialColorSurface" />
<corners android:topLeftRadius="@dimen/volume_dialog_background_corner_radius"
android:topRightRadius="@dimen/volume_dialog_background_corner_radius"/>
diff --git a/packages/SystemUI/res/drawable/volume_dialog_spacer.xml b/packages/SystemUI/res/drawable/volume_dialog_spacer.xml
deleted file mode 100644
index 3c60784cf6b6..000000000000
--- a/packages/SystemUI/res/drawable/volume_dialog_spacer.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- Copyright (C) 2024 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <size
- android:width="@dimen/volume_dialog_spacing"
- android:height="@dimen/volume_dialog_spacing" />
- <solid android:color="@color/transparent" />
-</shape>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml b/packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml
index 7c59aad10c91..71c77a56b6a8 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml
@@ -44,13 +44,13 @@
android:layout_height="wrap_content">
<com.android.systemui.statusbar.notification.row.FooterViewButton
- android:id="@+id/settings_button"
+ android:id="@+id/history_button"
style="@style/TextAppearance.NotificationFooterButtonRedesign"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:background="@drawable/notif_footer_btn_background"
- android:contentDescription="@string/notification_settings_button_description"
- android:drawableStart="@drawable/notif_footer_btn_settings"
+ android:contentDescription="@string/notification_history_button_description"
+ android:drawableStart="@drawable/notif_footer_btn_history"
android:focusable="true"
app:layout_constraintStart_toStartOf="parent" />
@@ -64,17 +64,17 @@
android:contentDescription="@string/accessibility_clear_all"
android:focusable="true"
android:text="@string/clear_all_notifications_text"
- app:layout_constraintEnd_toStartOf="@id/history_button"
- app:layout_constraintStart_toEndOf="@id/settings_button" />
+ app:layout_constraintEnd_toStartOf="@id/settings_button"
+ app:layout_constraintStart_toEndOf="@id/history_button" />
<com.android.systemui.statusbar.notification.row.FooterViewButton
- android:id="@+id/history_button"
+ android:id="@+id/settings_button"
style="@style/TextAppearance.NotificationFooterButtonRedesign"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:background="@drawable/notif_footer_btn_background"
- android:contentDescription="@string/notification_history_button_description"
- android:drawableStart="@drawable/notif_footer_btn_history"
+ android:contentDescription="@string/notification_settings_button_description"
+ android:drawableStart="@drawable/notif_footer_btn_settings"
android:focusable="true"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index f187ce62ddb2..694357d534fb 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -13,58 +13,70 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/volume_dialog_root"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ app:layoutDescription="@xml/volume_dialog_scene">
- <LinearLayout
- android:id="@+id/volume_dialog_container"
+ <View
+ android:id="@+id/volume_dialog_background"
+ android:layout_width="@dimen/volume_dialog_width"
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/volume_dialog_background_vertical_margin"
+ android:layout_marginBottom="@dimen/volume_dialog_background_vertical_margin"
+ android:background="@drawable/volume_dialog_background"
+ app:layout_constraintBottom_toBottomOf="@id/volume_dialog_settings"
+ app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintTop_toTopOf="@id/volume_ringer_and_drawer_container" />
+
+ <include
+ android:id="@id/volume_ringer_and_drawer_container"
+ layout="@layout/volume_ringer_drawer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical|end"
- android:divider="@drawable/volume_dialog_floating_sliders_spacer"
- android:orientation="horizontal"
- android:showDividers="middle|end|beginning">
-
- <LinearLayout
- android:id="@+id/volume_dialog_floating_sliders_container"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:divider="@drawable/volume_dialog_floating_sliders_spacer"
- android:gravity="bottom"
- android:orientation="horizontal"
- android:paddingBottom="@dimen/volume_dialog_floating_sliders_bottom_padding"
- android:showDividers="middle" />
+ android:layout_marginBottom="@dimen/volume_dialog_components_spacing"
+ app:layout_constraintBottom_toTopOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="1" />
- <LinearLayout
- android:id="@+id/volume_dialog"
- android:layout_width="@dimen/volume_dialog_width"
- android:layout_height="wrap_content"
- android:background="@drawable/volume_dialog_background"
- android:clipChildren="false"
- android:clipToOutline="false"
- android:clipToPadding="false"
- android:divider="@drawable/volume_dialog_spacer"
- android:gravity="center_horizontal"
- android:orientation="vertical"
- android:paddingVertical="@dimen/volume_dialog_vertical_padding"
- android:showDividers="middle">
+ <include
+ android:id="@+id/volume_dialog_main_slider_container"
+ layout="@layout/volume_dialog_slider" />
- <include layout="@layout/volume_ringer_drawer" />
+ <ImageButton
+ android:id="@+id/volume_dialog_settings"
+ android:layout_width="@dimen/volume_dialog_button_size"
+ android:layout_height="@dimen/volume_dialog_button_size"
+ android:layout_marginTop="@dimen/volume_dialog_components_spacing"
+ android:background="@drawable/ripple_drawable_20dp"
+ android:contentDescription="@string/accessibility_volume_settings"
+ android:soundEffectsEnabled="false"
+ android:src="@drawable/horizontal_ellipsis"
+ android:tint="?androidprv:attr/materialColorPrimary"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintTop_toBottomOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintVertical_bias="0" />
- <include layout="@layout/volume_dialog_slider" />
+ <LinearLayout
+ android:id="@+id/volume_dialog_floating_sliders_container"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/volume_dialog_floating_sliders_vertical_padding_negative"
+ android:layout_marginBottom="@dimen/volume_dialog_floating_sliders_vertical_padding_negative"
+ android:divider="@drawable/volume_dialog_floating_sliders_spacer"
+ android:gravity="bottom"
+ android:orientation="horizontal"
+ android:showDividers="middle|beginning|end"
+ app:layout_constraintBottom_toBottomOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintEnd_toStartOf="@id/volume_dialog_background"
+ app:layout_constraintTop_toTopOf="@id/volume_dialog_main_slider_container" />
- <ImageButton
- android:id="@+id/volume_dialog_settings"
- android:layout_width="@dimen/volume_dialog_button_size"
- android:layout_height="@dimen/volume_dialog_button_size"
- android:background="@drawable/ripple_drawable_20dp"
- android:contentDescription="@string/accessibility_volume_settings"
- android:soundEffectsEnabled="false"
- android:src="@drawable/horizontal_ellipsis"
- android:tint="?androidprv:attr/materialColorPrimary" />
- </LinearLayout>
- </LinearLayout>
-</FrameLayout> \ No newline at end of file
+</androidx.constraintlayout.motion.widget.MotionLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
index 7c266e60b503..b71c4700c0fa 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
@@ -19,13 +19,10 @@
android:id="@+id/volume_ringer_and_drawer_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center"
- android:paddingLeft="@dimen/volume_dialog_ringer_horizontal_padding"
- android:paddingRight="@dimen/volume_dialog_ringer_horizontal_padding"
- android:layoutDirection="ltr"
- android:clipToPadding="false"
android:clipChildren="false"
- android:background="@drawable/volume_background_top">
+ android:clipToPadding="false"
+ android:gravity="center"
+ android:layoutDirection="ltr">
<!-- Drawer view, invisible by default. -->
<FrameLayout
@@ -37,10 +34,10 @@
<!-- View that is animated to a tapped ringer selection, so it appears selected. -->
<FrameLayout
android:id="@+id/volume_drawer_selection_background"
- android:alpha="0.0"
android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size"
android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size"
android:layout_gravity="bottom|right"
+ android:alpha="0.0"
android:background="@drawable/volume_drawer_selection_bg" />
<LinearLayout
@@ -65,7 +62,6 @@
android:background="@drawable/volume_drawer_selection_bg"
android:contentDescription="@string/volume_ringer_change"
android:gravity="center"
- android:padding="@dimen/volume_dialog_ringer_horizontal_padding"
android:src="@drawable/ic_volume_media"
android:tint="?androidprv:attr/materialColorOnPrimary" />
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 4a53df9c2f29..75bee9f9266a 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -46,8 +46,6 @@
<dimen name="lockscreen_shade_max_over_scroll_amount">32dp</dimen>
- <dimen name="status_view_margin_horizontal">8dp</dimen>
-
<!-- Lockscreen shade transition values -->
<dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
<dimen name="lockscreen_shade_full_transition_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index 707bc9e535ff..f73e91af8aff 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -16,8 +16,6 @@
-->
<resources>
<dimen name="notification_panel_margin_horizontal">48dp</dimen>
- <dimen name="status_view_margin_horizontal">62dp</dimen>
-
<!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two
pages is margin * 2, and that makes tiles page not appear immediately after user swipes to
the side -->
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 8583f0549960..41bb37efa623 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -22,8 +22,6 @@
<dimen name="keyguard_split_shade_top_margin">72dp</dimen>
- <dimen name="status_view_margin_horizontal">24dp</dimen>
-
<dimen name="qs_media_session_height_expanded">184dp</dimen>
<dimen name="qs_content_horizontal_padding">40dp</dimen>
<dimen name="qs_horizontal_margin">40dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index 9248d585bba7..eb570b8f76d6 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -20,8 +20,6 @@
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
<resources>
- <dimen name="status_view_margin_horizontal">124dp</dimen>
-
<dimen name="large_screen_shade_header_left_padding">24dp</dimen>
<dimen name="qqs_layout_padding_bottom">40dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7af005721e13..4954f9122788 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1955,8 +1955,6 @@
<dimen name="dream_overlay_entry_y_offset">40dp</dimen>
<dimen name="dream_overlay_exit_y_offset">40dp</dimen>
- <dimen name="status_view_margin_horizontal">0dp</dimen>
-
<!-- Media output broadcast dialog QR code picture size -->
<dimen name="media_output_qrcode_size">216dp</dimen>
<dimen name="media_output_broadcast_info">21dp</dimen>
@@ -2060,26 +2058,29 @@
<dimen name="contextual_edu_dialog_elevation">2dp</dimen>
<!-- Volume start -->
- <dimen name="volume_dialog_background_corner_radius">30dp</dimen>
<dimen name="volume_dialog_width">60dp</dimen>
- <dimen name="volume_dialog_vertical_padding">10dp</dimen>
+
+ <dimen name="volume_dialog_background_corner_radius">30dp</dimen>
+ <dimen name="volume_dialog_background_vertical_margin">-10dp</dimen>
+
<dimen name="volume_dialog_components_spacing">8dp</dimen>
<dimen name="volume_dialog_floating_sliders_spacing">8dp</dimen>
<dimen name="volume_dialog_floating_sliders_vertical_padding">10dp</dimen>
+ <dimen name="volume_dialog_floating_sliders_vertical_padding_negative">-10dp</dimen>
<dimen name="volume_dialog_floating_sliders_horizontal_padding">4dp</dimen>
- <dimen name="volume_dialog_spacing">4dp</dimen>
<dimen name="volume_dialog_button_size">48dp</dimen>
- <dimen name="volume_dialog_floating_sliders_bottom_padding">48dp</dimen>
<dimen name="volume_dialog_slider_width">52dp</dimen>
<dimen name="volume_dialog_slider_height">254dp</dimen>
- <dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
- <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
+ <fraction name="volume_dialog_half_opened_bias">0.2</fraction>
+
+ <dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
- <dimen name="volume_dialog_ringer_horizontal_padding">10dp</dimen>
<dimen name="volume_dialog_ringer_drawer_button_size">40dp</dimen>
<dimen name="volume_dialog_ringer_drawer_button_icon_radius">10dp</dimen>
- <dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
<dimen name="volume_dialog_ringer_selected_button_background_radius">20dp</dimen>
+
+ <dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
+ <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
<!-- Volume end -->
</resources>
diff --git a/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml b/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
new file mode 100644
index 000000000000..9018e5b7ed92
--- /dev/null
+++ b/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ConstraintSet xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/volume_dialog_constraint_set">
+
+ <Constraint
+ android:id="@id/volume_dialog_main_slider_container"
+ android:layout_width="@dimen/volume_dialog_slider_width"
+ android:layout_height="@dimen/volume_dialog_slider_height"
+ android:layout_marginEnd="@dimen/volume_dialog_components_spacing"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.5" />
+</ConstraintSet> \ No newline at end of file
diff --git a/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml b/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
new file mode 100644
index 000000000000..297c38873164
--- /dev/null
+++ b/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ConstraintSet xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/volume_dialog_half_folded_constraint_set">
+
+ <Constraint
+ android:id="@id/volume_dialog_main_slider_container"
+ android:layout_width="@dimen/volume_dialog_slider_width"
+ android:layout_height="@dimen/volume_dialog_slider_height"
+ android:layout_marginEnd="@dimen/volume_dialog_components_spacing"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="@fraction/volume_dialog_half_opened_bias" />
+</ConstraintSet> \ No newline at end of file
diff --git a/packages/SystemUI/res/xml/volume_dialog_scene.xml b/packages/SystemUI/res/xml/volume_dialog_scene.xml
new file mode 100644
index 000000000000..b813474490bb
--- /dev/null
+++ b/packages/SystemUI/res/xml/volume_dialog_scene.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<MotionScene xmlns:motion="http://schemas.android.com/apk/res-auto">
+
+ <Transition
+ motion:autoTransition="none"
+ motion:constraintSetEnd="@id/volume_dialog_half_folded_constraint_set"
+ motion:constraintSetStart="@id/volume_dialog_constraint_set"
+ motion:duration="150" />
+
+ <Include motion:constraintSet="@xml/volume_dialog_constraint_set" />
+ <Include motion:constraintSet="@xml/volume_dialog_half_folded_constraint_set" />
+</MotionScene> \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 95830b5f4ed7..add459b84ac1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -44,7 +44,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.shade.data.repository.ShadePositionRepository;
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository;
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -66,7 +66,7 @@ public class KeyguardDisplayManager {
private final DisplayManager mDisplayService;
private final DisplayTracker mDisplayTracker;
private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
- private final Provider<ShadePositionRepository> mShadePositionRepositoryProvider;
+ private final Provider<ShadeDisplaysRepository> mShadePositionRepositoryProvider;
private final ConnectedDisplayKeyguardPresentation.Factory
mConnectedDisplayKeyguardPresentationFactory;
private final Context mContext;
@@ -112,7 +112,7 @@ public class KeyguardDisplayManager {
KeyguardStateController keyguardStateController,
ConnectedDisplayKeyguardPresentation.Factory
connectedDisplayKeyguardPresentationFactory,
- Provider<ShadePositionRepository> shadePositionRepositoryProvider,
+ Provider<ShadeDisplaysRepository> shadePositionRepositoryProvider,
@Application CoroutineScope appScope) {
mContext = context;
mNavigationBarControllerLazy = navigationBarControllerLazy;
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index fb00d6e16dcc..db4d613a9101 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -52,6 +52,8 @@ import com.android.systemui.util.display.DisplayHelper;
import com.google.common.util.concurrent.ListenableFuture;
+import kotlin.Unit;
+
import kotlinx.coroutines.Job;
import java.util.Collection;
@@ -87,7 +89,7 @@ public class TouchMonitor {
private final ConfigurationInteractor mConfigurationInteractor;
private final Lifecycle mLifecycle;
- private Rect mExclusionRect = null;
+ private Rect mExclusionRect = new Rect();
private ISystemGestureExclusionListener mGestureExclusionListener;
@@ -298,9 +300,18 @@ public class TouchMonitor {
public void onSystemGestureExclusionChanged(int displayId,
Region systemGestureExclusion,
Region systemGestureExclusionUnrestricted) {
- mExclusionRect = systemGestureExclusion.getBounds();
+ final Rect bounds = systemGestureExclusion.getBounds();
+ if (!mExclusionRect.equals(bounds)) {
+ mExclusionRect = bounds;
+ mLogger.i(msg -> "Exclusion rect updated to " + msg.getStr1(),
+ msg -> {
+ msg.setStr1(bounds.toString());
+ return Unit.INSTANCE;
+ });
+ }
}
};
+ mLogger.i("Registering system gesture exclusion listener");
mWindowManagerService.registerSystemGestureExclusionListener(
mGestureExclusionListener, mDisplayId);
} catch (RemoteException e) {
@@ -320,11 +331,12 @@ public class TouchMonitor {
* Destroys any active {@link InputSession}.
*/
private void stopMonitoring(boolean force) {
- mExclusionRect = null;
+ mExclusionRect = new Rect();
if (bouncerAreaExclusion()) {
mBackgroundExecutor.execute(() -> {
try {
if (mGestureExclusionListener != null) {
+ mLogger.i("Unregistering system gesture exclusion listener");
mWindowManagerService.unregisterSystemGestureExclusionListener(
mGestureExclusionListener, mDisplayId);
mGestureExclusionListener = null;
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 4e0e11226faa..5ecf2e6b2551 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -29,6 +29,7 @@ import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
@@ -43,6 +44,7 @@ abstract class BaseCommunalViewModel(
val communalSceneInteractor: CommunalSceneInteractor,
private val communalInteractor: CommunalInteractor,
val mediaHost: MediaHost,
+ val mediaCarouselController: MediaCarouselController,
) {
val currentScene: Flow<SceneKey> = communalSceneInteractor.currentScene
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 ccff23003aa0..736ed5c7d336 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
@@ -46,6 +46,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
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.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.res.R
@@ -82,7 +83,14 @@ constructor(
private val accessibilityManager: AccessibilityManager,
private val packageManager: PackageManager,
@Named(LAUNCHER_PACKAGE) private val launcherPackage: String,
-) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
+ mediaCarouselController: MediaCarouselController,
+) :
+ BaseCommunalViewModel(
+ communalSceneInteractor,
+ communalInteractor,
+ mediaHost,
+ mediaCarouselController,
+ ) {
private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index 623937305921..34962305ff2a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -31,6 +31,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransit
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
@@ -41,6 +42,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
@@ -60,7 +62,7 @@ constructor(
glanceableHubToDreamTransitionViewModel: GlanceableHubToDreamingTransitionViewModel,
communalInteractor: CommunalInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
- keyguardTransitionInteractor: KeyguardTransitionInteractor
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
/**
* Snaps to [CommunalScenes.Communal], showing the glanceable hub immediately without any
@@ -89,7 +91,7 @@ constructor(
keyguardTransitionInteractor
.transition(
edge = Edge.create(from = Scenes.Communal),
- edgeWithoutSceneContainer = Edge.create(from = KeyguardState.GLANCEABLE_HUB)
+ edgeWithoutSceneContainer = Edge.create(from = KeyguardState.GLANCEABLE_HUB),
)
.filter {
it.to == KeyguardState.OCCLUDED &&
@@ -114,21 +116,24 @@ constructor(
// from
// the hub, then pressing power twice to go back to the lock screen.
communalSceneInteractor.isCommunalVisible,
- merge(
- lockscreenToGlanceableHubTransitionViewModel.showUmo,
- glanceableHubToLockscreenTransitionViewModel.showUmo,
- dreamToGlanceableHubTransitionViewModel.showUmo,
- glanceableHubToDreamTransitionViewModel.showUmo,
- showUmoFromOccludedToGlanceableHub,
- showUmoFromGlanceableHubToOccluded,
- )
- .onStart { emit(false) }
- )
+ // TODO(b/378942852): polish UMO transitions when scene container is enabled
+ if (SceneContainerFlag.isEnabled) flowOf(true)
+ else
+ merge(
+ lockscreenToGlanceableHubTransitionViewModel.showUmo,
+ glanceableHubToLockscreenTransitionViewModel.showUmo,
+ dreamToGlanceableHubTransitionViewModel.showUmo,
+ glanceableHubToDreamTransitionViewModel.showUmo,
+ showUmoFromOccludedToGlanceableHub,
+ showUmoFromGlanceableHubToOccluded,
+ )
+ .onStart { emit(false) },
+ ),
)
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = false
+ initialValue = false,
)
/** Whether to show communal when exiting the occluded state. */
@@ -145,8 +150,7 @@ constructor(
val recentsBackgroundColor: Flow<Color?> =
combine(showCommunalFromOccluded, communalColors.backgroundColor) {
showCommunalFromOccluded,
- backgroundColor,
- ->
+ backgroundColor ->
if (showCommunalFromOccluded) {
backgroundColor
} else {
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 7990450f6ac8..9cd6465266d4 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,7 @@ import android.content.res.Resources
import android.os.Bundle
import android.view.View
import android.view.accessibility.AccessibilityNodeInfo
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -38,6 +39,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
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.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
@@ -72,7 +74,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
/** The default view model used for showing the communal hub. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -95,7 +96,14 @@ constructor(
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@CommunalLog logBuffer: LogBuffer,
private val metricsLogger: CommunalMetricsLogger,
-) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
+ mediaCarouselController: MediaCarouselController,
+) :
+ BaseCommunalViewModel(
+ communalSceneInteractor,
+ communalInteractor,
+ mediaHost,
+ mediaCarouselController,
+ ) {
private val logger = Logger(logBuffer, "CommunalViewModel")
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index b74ca035a229..35eed5e6a6d9 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -35,6 +35,7 @@ import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
@@ -363,6 +364,9 @@ constructor(
private val interactor: DeviceUnlockedInteractor,
) : CoreStartable {
override fun start() {
+ if (!SceneContainerFlag.isEnabled)
+ return
+
applicationScope.launch { interactor.activate() }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 1ffbbd2a9f32..b330ba376810 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -65,6 +65,9 @@ import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
import com.android.systemui.navigationbar.gestural.domain.TaskMatcher;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -162,6 +165,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
private TouchMonitor mTouchMonitor;
+ private final SceneInteractor mSceneInteractor;
private final CommunalInteractor mCommunalInteractor;
private boolean mCommunalAvailable;
@@ -378,6 +382,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
KeyguardUpdateMonitor keyguardUpdateMonitor,
ScrimManager scrimManager,
CommunalInteractor communalInteractor,
+ SceneInteractor sceneInteractor,
SystemDialogsCloser systemDialogsCloser,
UiEventLogger uiEventLogger,
@Named(DREAM_TOUCH_INSET_MANAGER) TouchInsetManager touchInsetManager,
@@ -405,6 +410,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mDreamOverlayCallbackController = dreamOverlayCallbackController;
mWindowTitle = windowTitle;
mCommunalInteractor = communalInteractor;
+ mSceneInteractor = sceneInteractor;
mSystemDialogsCloser = systemDialogsCloser;
mGestureInteractor = gestureInteractor;
mDreamOverlayComponentFactory = dreamOverlayComponentFactory;
@@ -551,9 +557,15 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
@Override
public void onWakeRequested() {
mUiEventLogger.log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START);
- mCommunalInteractor.changeScene(CommunalScenes.Communal,
- "dream wake requested",
- null);
+ if (SceneContainerFlag.isEnabled()) {
+ // Scene interactor can only be modified on main thread.
+ mExecutor.execute(() -> mSceneInteractor.changeScene(Scenes.Communal,
+ "dream wake redirect to communal"));
+ } else {
+ mCommunalInteractor.changeScene(CommunalScenes.Communal,
+ "dream wake requested",
+ null);
+ }
}
private void updateGestureBlockingLocked() {
@@ -617,7 +629,13 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mSystemDialogsCloser.closeSystemDialogs();
// Hide glanceable hub (this is a nop if glanceable hub is not open).
- mCommunalInteractor.changeScene(CommunalScenes.Blank, "dream come to front", null);
+ if (SceneContainerFlag.isEnabled()) {
+ // Scene interactor can only be modified on main thread.
+ mExecutor.execute(
+ () -> mSceneInteractor.changeScene(Scenes.Dream, "closing hub to go to dream"));
+ } else {
+ mCommunalInteractor.changeScene(CommunalScenes.Blank, "dream come to front", null);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
index b8ac0d282229..5a9e52ae5655 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
@@ -54,6 +54,8 @@ constructor(
shadeInteractor.shadeMode,
) { isDeviceUnlocked, shadeMode ->
buildList {
+ add(Swipe.Start to Scenes.Communal)
+
val bouncerOrGone =
if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer
add(Swipe.Up to bouncerOrGone)
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
index ed7d1823648a..316964a47753 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
@@ -83,9 +83,6 @@ constructor(
interactionState == TileInteractionState.LONG_CLICKED &&
animationState == TileAnimationState.ACTIVITY_LAUNCH ->
TileHapticsState.LONG_PRESS
- interactionState == TileInteractionState.LONG_CLICKED &&
- !tileViewModel.currentState.handlesLongClick ->
- TileHapticsState.FAILED_LONGPRESS
else -> TileHapticsState.NO_HAPTICS
}
}
@@ -102,7 +99,6 @@ constructor(
TileHapticsState.TOGGLE_ON -> MSDLToken.SWITCH_ON
TileHapticsState.TOGGLE_OFF -> MSDLToken.SWITCH_OFF
TileHapticsState.LONG_PRESS -> MSDLToken.LONG_PRESS
- TileHapticsState.FAILED_LONGPRESS -> MSDLToken.FAILURE
TileHapticsState.NO_HAPTICS -> null
}
tokenToPlay?.let {
@@ -154,7 +150,6 @@ constructor(
TOGGLE_ON,
TOGGLE_OFF,
LONG_PRESS,
- FAILED_LONGPRESS,
NO_HAPTICS,
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2ee9ddb0e453..01ec4d026646 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2875,7 +2875,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
- mKeyguardTransitions.startKeyguardTransition(showing, aodShowing);
+ startKeyguardTransition(showing, aodShowing);
} else {
try {
@@ -3019,7 +3019,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
final int keyguardFlag = flags;
mUiBgExecutor.execute(() -> {
if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
- mKeyguardTransitions.startKeyguardTransition(
+ startKeyguardTransition(
false /* keyguardShowing */, false /* aodShowing */);
return;
}
@@ -3035,6 +3035,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
};
+ private void startKeyguardTransition(boolean keyguardShowing, boolean aodShowing) {
+ mKeyguardTransitions.startKeyguardTransition(keyguardShowing, aodShowing);
+ }
+
private final Runnable mHideAnimationFinishedRunnable = () -> {
Log.e(TAG, "mHideAnimationFinishedRunnable#run");
mHideAnimationRunning = false;
@@ -3490,8 +3494,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mSurfaceBehindRemoteAnimationRequested = true;
if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
- mKeyguardTransitions.startKeyguardTransition(
- false /* keyguardShowing */, false /* aodShowing */);
+ startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */);
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 2914cb9fdfdc..a137d6cf91ec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -122,10 +122,7 @@ constructor(
if (visible) {
if (enableNewKeyguardShellTransitions) {
- keyguardTransitions.startKeyguardTransition(
- false /* keyguardShowing */,
- false, /* aodShowing */
- )
+ startKeyguardTransition(false, /* keyguardShowing */ false /* aodShowing */)
isKeyguardGoingAway = true
return
}
@@ -233,7 +230,7 @@ constructor(
"aodVisible=$aodVisible).",
)
if (enableNewKeyguardShellTransitions) {
- keyguardTransitions.startKeyguardTransition(lockscreenShowing, aodVisible)
+ startKeyguardTransition(lockscreenShowing, aodVisible)
} else {
activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
}
@@ -241,6 +238,10 @@ constructor(
this.isAodVisible = aodVisible
}
+ private fun startKeyguardTransition(keyguardShowing: Boolean, aodShowing: Boolean) {
+ keyguardTransitions.startKeyguardTransition(keyguardShowing, aodShowing)
+ }
+
private fun endKeyguardGoingAwayAnimation() {
if (!isKeyguardGoingAway) {
Log.d(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 914fdd20e48e..6c03b2489380 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -151,7 +151,5 @@ object KeyguardPreviewClockViewBinder {
cs.applyTo(rootView)
}
- private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
- private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
private const val TAG = "KeyguardPreviewClockViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 08c3f153bc4e..4c23adfe92e8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -29,6 +29,7 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.customization.R as customR
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -114,14 +115,14 @@ constructor(
START,
PARENT_ID,
START,
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
+ context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal),
)
connect(
nicId,
END,
PARENT_ID,
END,
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
+ context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal),
)
constrainHeight(
nicId,
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 c009720feaeb..6c98d5b01e4e 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
@@ -221,7 +221,9 @@ constructor(
PARENT_ID,
START,
context.resources.getDimensionPixelSize(customR.dimen.clock_padding_start) +
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
+ context.resources.getDimensionPixelSize(
+ customR.dimen.status_view_margin_horizontal
+ ),
)
val smallClockTopMargin = keyguardClockViewModel.getSmallClockTopMargin()
create(R.id.small_clock_guideline_top, ConstraintSet.HORIZONTAL_GUIDELINE)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
index 0782846f52ae..f0d21f22c5ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
@@ -28,6 +28,7 @@ import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.systemui.customization.R as customR
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
@@ -42,7 +43,7 @@ class SplitShadeMediaSection
constructor(
@ShadeDisplayAware private val context: Context,
private val notificationPanelView: NotificationPanelView,
- private val keyguardMediaController: KeyguardMediaController
+ private val keyguardMediaController: KeyguardMediaController,
) : KeyguardSection() {
private val mediaContainerId = R.id.status_view_media_container
@@ -62,7 +63,7 @@ constructor(
val horizontalPadding =
padding +
context.resources.getDimensionPixelSize(
- R.dimen.status_view_margin_horizontal
+ customR.dimen.status_view_margin_horizontal
)
setPaddingRelative(horizontalPadding, padding, horizontalPadding, padding)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index e30ddc69b19d..de0927ec27cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
+import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
@@ -59,10 +60,9 @@ constructor(
/** Whether the weather area should be visible. */
val isWeatherVisible: StateFlow<Boolean> =
- combine(
+ combine(isWeatherEnabled, keyguardClockViewModel.hasCustomWeatherDataDisplay) {
isWeatherEnabled,
- keyguardClockViewModel.hasCustomWeatherDataDisplay,
- ) { isWeatherEnabled, clockIncludesCustomWeatherDisplay ->
+ clockIncludesCustomWeatherDisplay ->
isWeatherVisible(
clockIncludesCustomWeatherDisplay = clockIncludesCustomWeatherDisplay,
isWeatherEnabled = isWeatherEnabled,
@@ -76,7 +76,7 @@ constructor(
clockIncludesCustomWeatherDisplay =
keyguardClockViewModel.hasCustomWeatherDataDisplay.value,
isWeatherEnabled = smartspaceInteractor.isWeatherEnabled.value,
- )
+ ),
)
private fun isWeatherVisible(
@@ -92,12 +92,12 @@ constructor(
companion object {
fun getSmartspaceStartMargin(context: Context): Int {
return context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) +
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal)
}
fun getSmartspaceEndMargin(context: Context): Int {
return context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end) +
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 9029563b6321..e3de6d5152e4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -62,6 +62,7 @@ import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.domain.interactor.DisableFlagsInteractor
import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.util.asIndenting
import com.android.systemui.util.kotlin.emitOnStart
@@ -93,7 +94,7 @@ constructor(
private val footerActionsController: FooterActionsController,
private val sysuiStatusBarStateController: SysuiStatusBarStateController,
deviceEntryInteractor: DeviceEntryInteractor,
- disableFlagsRepository: DisableFlagsRepository,
+ DisableFlagsInteractor: DisableFlagsInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator,
@ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
@@ -182,8 +183,8 @@ constructor(
val isQsEnabled by
hydrator.hydratedStateOf(
traceName = "isQsEnabled",
- initialValue = disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled(),
- source = disableFlagsRepository.disableFlags.map { it.isQuickSettingsEnabled() },
+ initialValue = DisableFlagsInteractor.disableFlags.value.isQuickSettingsEnabled(),
+ source = DisableFlagsInteractor.disableFlags.map { it.isQuickSettingsEnabled() },
)
var isInSplitShade by mutableStateOf(false)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 0e09ad29f4fd..dbad60265645 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.panels.ui.compose.infinitegrid
import android.graphics.drawable.Animatable
+import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.text.TextUtils
import androidx.compose.animation.animateColorAsState
@@ -228,7 +229,14 @@ fun SmallTileContent(
}
}
}
- is Icon.Loaded -> rememberDrawablePainter(loadedDrawable)
+ is Icon.Loaded -> {
+ LaunchedEffect(loadedDrawable) {
+ if (loadedDrawable is AnimatedVectorDrawable) {
+ loadedDrawable.forceAnimationOnUI()
+ }
+ }
+ rememberDrawablePainter(loadedDrawable)
+ }
}
Image(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 301ab2bcdd65..8f6c4e743269 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -1429,7 +1429,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
void makeOverlayToast(int stringId) {
final Resources res = mContext.getResources();
- final SystemUIToast systemUIToast = mToastFactory.createToast(mContext,
+ final SystemUIToast systemUIToast = mToastFactory.createToast(mContext, mContext,
res.getString(stringId), mContext.getPackageName(), UserHandle.myUserId(),
res.getConfiguration().orientation);
if (systemUIToast == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
index 667827ac4724..c96ea03f057a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
@@ -138,6 +138,7 @@ constructor(
Overlays.QuickSettingsShade -> false
Scenes.Bouncer -> false
Scenes.Communal -> true
+ Scenes.Dream -> false
Scenes.Gone -> true
Scenes.Lockscreen -> true
Scenes.QuickSettings -> false
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
index 41a3c8aff6cf..b89eb5c762e0 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
@@ -23,6 +23,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import dagger.Binds
@@ -46,6 +47,7 @@ class HomeSceneFamilyResolver
constructor(
@Application private val applicationScope: CoroutineScope,
deviceEntryInteractor: DeviceEntryInteractor,
+ keyguardInteractor: KeyguardInteractor,
keyguardEnabledInteractor: KeyguardEnabledInteractor,
) : SceneResolver {
override val targetFamily: SceneKey = SceneFamilies.Home
@@ -56,6 +58,7 @@ constructor(
deviceEntryInteractor.canSwipeToEnter,
deviceEntryInteractor.isDeviceEntered,
deviceEntryInteractor.isUnlocked,
+ keyguardInteractor.isDreamingWithOverlay,
transform = ::homeScene,
)
.stateIn(
@@ -67,7 +70,8 @@ constructor(
canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value,
isDeviceEntered = deviceEntryInteractor.isDeviceEntered.value,
isUnlocked = deviceEntryInteractor.isUnlocked.value,
- )
+ isDreamingWithOverlay = false,
+ ),
)
override fun includesScene(scene: SceneKey): Boolean = scene in homeScenes
@@ -77,8 +81,11 @@ constructor(
canSwipeToEnter: Boolean?,
isDeviceEntered: Boolean,
isUnlocked: Boolean,
+ isDreamingWithOverlay: Boolean,
): SceneKey =
when {
+ // Dream can run even if Keyguard is disabled, thus it has the highest priority here.
+ isDreamingWithOverlay -> Scenes.Dream
!isKeyguardEnabled -> Scenes.Gone
canSwipeToEnter == true -> Scenes.Lockscreen
!isDeviceEntered -> Scenes.Lockscreen
@@ -91,6 +98,9 @@ constructor(
setOf(
Scenes.Gone,
Scenes.Lockscreen,
+ // Dream is a home scene as the dream activity occludes keyguard and can show the
+ // shade on top.
+ Scenes.Dream,
)
}
}
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 daeaaa52fd94..9125d7e8bb0e 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
@@ -62,6 +62,7 @@ import com.android.systemui.scene.session.shared.SessionStorage
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -217,7 +218,9 @@ constructor(
sceneInteractor.transitionState.mapNotNull { state ->
when (state) {
is ObservableTransitionState.Idle -> {
- if (state.currentScene != Scenes.Gone) {
+ if (state.currentScene == Scenes.Dream) {
+ false to "dream is showing"
+ } else if (state.currentScene != Scenes.Gone) {
true to "scene is not Gone"
} else if (state.currentOverlays.isNotEmpty()) {
true to "overlay is shown"
@@ -228,21 +231,30 @@ constructor(
is ObservableTransitionState.Transition -> {
if (state.fromContent == Scenes.Gone) {
true to "scene transitioning away from Gone"
+ } else if (state.fromContent == Scenes.Dream) {
+ true to "scene transitioning away from dream"
} else {
null
}
}
}
},
+ sceneInteractor.transitionState.map { state ->
+ state.isTransitioningFromOrTo(Scenes.Communal) ||
+ state.isIdle(Scenes.Communal)
+ },
headsUpInteractor.isHeadsUpOrAnimatingAway,
occlusionInteractor.invisibleDueToOcclusion,
alternateBouncerInteractor.isVisible,
) {
visibilityForTransitionState,
+ isCommunalShowing,
isHeadsUpOrAnimatingAway,
invisibleDueToOcclusion,
isAlternateBouncerVisible ->
when {
+ isCommunalShowing ->
+ true to "on or transitioning to/from communal"
isHeadsUpOrAnimatingAway -> true to "showing a HUN"
isAlternateBouncerVisible -> true to "showing alternate bouncer"
invisibleDueToOcclusion -> false to "invisible due to occlusion"
@@ -266,6 +278,7 @@ constructor(
handleSimUnlock()
handleDeviceUnlockStatus()
handlePowerState()
+ handleDreamState()
handleShadeTouchability()
}
@@ -506,6 +519,31 @@ constructor(
}
}
+ private fun handleDreamState() {
+ applicationScope.launch {
+ keyguardInteractor.isAbleToDream
+ .sample(sceneInteractor.transitionState, ::Pair)
+ .collect { (isAbleToDream, transitionState) ->
+ if (transitionState.isIdle(Scenes.Communal)) {
+ // The dream is automatically started underneath the hub, don't transition
+ // to dream when this is happening as communal is still visible on top.
+ return@collect
+ }
+ if (isAbleToDream) {
+ switchToScene(
+ targetSceneKey = Scenes.Dream,
+ loggingReason = "dream started",
+ )
+ } else {
+ switchToScene(
+ targetSceneKey = SceneFamilies.Home,
+ loggingReason = "dream stopped",
+ )
+ }
+ }
+ }
+ }
+
private fun handleShadeTouchability() {
applicationScope.launch {
shadeInteractor.isShadeTouchable
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 3a90d2b9df7b..503d0bfbc301 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -235,8 +235,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
if (mBrightnessWarningToast.isToastActive()) {
return;
}
- mBrightnessWarningToast.show(mView.getContext(),
- R.string.quick_settings_brightness_unable_adjust_msg);
+ mBrightnessWarningToast.show(mView.getContext(), resId);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt
index dfbdaa62ec44..40260d0ca29f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt
@@ -37,11 +37,14 @@ constructor(
private var toastView: View? = null
fun show(viewContext: Context, @StringRes resId: Int) {
+ if (isToastActive()) {
+ return
+ }
val res = viewContext.resources
// Show the brightness warning toast with passing the toast inflation required context,
// userId and resId from SystemUI package.
val systemUIToast = toastFactory.createToast(
- viewContext,
+ viewContext, viewContext,
res.getString(resId), viewContext.packageName, viewContext.getUserId(),
res.configuration.orientation
)
@@ -79,13 +82,15 @@ constructor(
val inAnimator = systemUIToast.inAnimation
inAnimator?.start()
- toastView!!.postDelayed({
+ toastView?.postDelayed({
val outAnimator = systemUIToast.outAnimation
if (outAnimator != null) {
outAnimator.start()
outAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animator: Animator) {
- windowManager.removeViewImmediate(toastView)
+ if (isToastActive()) {
+ windowManager.removeViewImmediate(toastView)
+ }
toastView = null
}
})
@@ -94,7 +99,7 @@ constructor(
}
fun isToastActive(): Boolean {
- return toastView != null && toastView!!.isAttachedToWindow
+ return toastView?.isAttachedToWindow == true
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 0e82bf82fdf9..4ccd2b93911e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3109,12 +3109,18 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
if (isTracking()) {
onTrackingStopped(true);
}
- if (isExpanded() && !mQsController.getExpanded()) {
+ if (isExpanded() && mBarState != KEYGUARD && !mQsController.getExpanded()) {
mShadeLog.d("Status Bar was long pressed. Expanding to QS.");
expandToQs();
} else {
- mShadeLog.d("Status Bar was long pressed. Expanding to Notifications.");
- expandToNotifications();
+ if (mBarState == KEYGUARD) {
+ mShadeLog.d("Lockscreen Status Bar was long pressed. Expanding to Notifications.");
+ mLockscreenShadeTransitionController.goToLockedShade(
+ /* expandedView= */null, /* needsQSAnimation= */false);
+ } else {
+ mShadeLog.d("Status Bar was long pressed. Expanding to Notifications.");
+ expandToNotifications();
+ }
}
}
@@ -5091,13 +5097,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
- if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
- event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
- if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
- mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
- }
- return true;
- }
// This touch session has already resulted in shade expansion. Ignore everything else.
if (ShadeExpandsOnStatusBarLongPress.isEnabled()
&& event.getActionMasked() != MotionEvent.ACTION_DOWN
@@ -5105,6 +5104,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mShadeLog.d("Touch has same down time as Status Bar long press. Ignoring.");
return false;
}
+ if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
+ event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
+ if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
+ mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
+ }
+ return true;
+ }
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
handled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 7a18d7caa13f..207439e1f374 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -28,6 +28,7 @@ import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.lifecycle.lifecycleScope
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.fragments.FragmentService
@@ -314,7 +315,7 @@ constructor(
private fun setKeyguardStatusViewConstraints(constraintSet: ConstraintSet) {
val statusViewMarginHorizontal =
- resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal)
constraintSet.apply {
setMargin(R.id.keyguard_status_view, START, statusViewMarginHorizontal)
setMargin(R.id.keyguard_status_view, END, statusViewMarginHorizontal)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index e15830eb22eb..fed4a26ab1ab 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -30,8 +30,8 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.ShadePositionRepository
-import com.android.systemui.shade.data.repository.ShadePositionRepositoryImpl
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.phone.ConfigurationForwarder
@@ -157,16 +157,16 @@ object ShadeDisplayAwareModule {
@SysUISingleton
@Provides
- fun provideShadePositionRepository(impl: ShadePositionRepositoryImpl): ShadePositionRepository {
+ fun provideShadePositionRepository(impl: ShadeDisplaysRepositoryImpl): ShadeDisplaysRepository {
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
return impl
}
@Provides
@IntoMap
- @ClassKey(ShadePositionRepositoryImpl::class)
+ @ClassKey(ShadeDisplaysRepositoryImpl::class)
fun provideShadePositionRepositoryAsCoreStartable(
- impl: ShadePositionRepositoryImpl
+ impl: ShadeDisplaysRepositoryImpl
): CoreStartable {
return if (ShadeWindowGoesAround.isEnabled) {
impl
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
index 802fc0edb533..506b4e9ab565 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
@@ -17,11 +17,11 @@
package com.android.systemui.shade
import android.view.Display
-import com.android.systemui.shade.data.repository.ShadePositionRepository
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.statusbar.commandline.Command
import java.io.PrintWriter
-class ShadePrimaryDisplayCommand(private val positionRepository: ShadePositionRepository) :
+class ShadePrimaryDisplayCommand(private val positionRepository: ShadeDisplaysRepository) :
Command {
override fun execute(pw: PrintWriter, args: List<String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
index 6fb3ca5f86d2..ae36e81c7b1f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
@@ -25,7 +25,7 @@ import javax.inject.Inject
/** Accepts touch events, detects long press, and calls ShadeViewController#onStatusBarLongPress. */
@SysUISingleton
-class LongPressGestureDetector
+class StatusBarLongPressGestureDetector
@Inject
constructor(context: Context, val shadeViewController: ShadeViewController) {
val gestureDetector =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
index 37210b90ee78..71c565816362 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
@@ -20,7 +20,7 @@ import android.view.Display
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-class FakeShadePositionRepository : ShadePositionRepository {
+class FakeShadeDisplayRepository : ShadeDisplaysRepository {
private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
override fun setDisplayId(displayId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
index 24c067ae2371..e920abac8ccc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
@@ -25,7 +25,7 @@ import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-interface ShadePositionRepository {
+interface ShadeDisplaysRepository {
/** ID of the display which currently hosts the shade */
val displayId: StateFlow<Int>
@@ -41,9 +41,9 @@ interface ShadePositionRepository {
/** Source of truth for the display currently holding the shade. */
@SysUISingleton
-class ShadePositionRepositoryImpl
+class ShadeDisplaysRepositoryImpl
@Inject
-constructor(private val commandRegistry: CommandRegistry) : ShadePositionRepository, CoreStartable {
+constructor(private val commandRegistry: CommandRegistry) : ShadeDisplaysRepository, CoreStartable {
private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
override val displayId: StateFlow<Int>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
index 4e7898d2fd2d..1055dcb55d5f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -30,7 +30,7 @@ import com.android.systemui.display.shared.model.DisplayWindowProperties
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.ShadeWindowLayoutParams
-import com.android.systemui.shade.data.repository.ShadePositionRepository
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.phone.ConfigurationForwarder
import javax.inject.Inject
@@ -38,13 +38,13 @@ import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext
-/** Handles Shade window display change when [ShadePositionRepository.displayId] changes. */
+/** Handles Shade window display change when [ShadeDisplaysRepository.displayId] changes. */
@SysUISingleton
class ShadeDisplaysInteractor
@Inject
constructor(
private val shadeRootView: WindowRootView,
- private val shadePositionRepository: ShadePositionRepository,
+ private val shadePositionRepository: ShadeDisplaysRepository,
@ShadeDisplayAware private val shadeContext: Context,
private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
@Background private val bgScope: CoroutineScope,
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 460bfbbcb3ab..a653ca2f80a9 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,7 +25,7 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.Edge
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.disableflags.domain.interactor.DisableFlagsInteractor
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
@@ -47,7 +47,7 @@ class ShadeInteractorImpl
constructor(
@Application val scope: CoroutineScope,
deviceProvisioningInteractor: DeviceProvisioningInteractor,
- disableFlagsRepository: DisableFlagsRepository,
+ disableFlagsInteractor: DisableFlagsInteractor,
dozeParams: DozeParameters,
keyguardRepository: KeyguardRepository,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
@@ -61,13 +61,13 @@ constructor(
BaseShadeInteractor by baseShadeInteractor,
ShadeModeInteractor by shadeModeInteractor {
override val isShadeEnabled: StateFlow<Boolean> =
- disableFlagsRepository.disableFlags
+ disableFlagsInteractor.disableFlags
.map { it.isShadeEnabled() }
.flowName("isShadeEnabled")
.stateIn(scope, SharingStarted.Eagerly, initialValue = false)
override val isQsEnabled: StateFlow<Boolean> =
- disableFlagsRepository.disableFlags
+ disableFlagsInteractor.disableFlags
.map { it.isQuickSettingsEnabled() }
.flowName("isQsEnabled")
.stateIn(scope, SharingStarted.Eagerly, initialValue = false)
@@ -114,7 +114,7 @@ constructor(
override val isExpandToQsEnabled: Flow<Boolean> =
combine(
- disableFlagsRepository.disableFlags,
+ disableFlagsInteractor.disableFlags,
isShadeEnabled,
keyguardRepository.isDozing,
userSetupRepository.isUserSetUp,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 33f0c64269cc..6bec86a67e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -177,8 +177,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
private float[] mMatrix;
private ColorMatrixColorFilter mMatrixColorFilter;
private Runnable mLayoutRunnable;
- private boolean mDismissed;
- private Runnable mOnDismissListener;
private boolean mIncreasedSize;
private boolean mShowsConversation;
private float mDozeAmount;
@@ -956,21 +954,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
mLayoutRunnable = runnable;
}
- public void setDismissed() {
- mDismissed = true;
- if (mOnDismissListener != null) {
- mOnDismissListener.run();
- }
- }
-
- public boolean isDismissed() {
- return mDismissed;
- }
-
- public void setOnDismissListener(Runnable onDismissListener) {
- mOnDismissListener = onDismissListener;
- }
-
@Override
public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
int areaTint = getTint(areas, this, tint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index da04f6edf9e2..b2ca33a4aecf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -705,6 +705,7 @@ public class StatusBarStateControllerImpl implements
final boolean onBouncer = currentScene.equals(Scenes.Bouncer);
final boolean onCommunal = currentScene.equals(Scenes.Communal);
final boolean onGone = currentScene.equals(Scenes.Gone);
+ final boolean onDream = currentScene.equals(Scenes.Dream);
final boolean onLockscreen = currentScene.equals(Scenes.Lockscreen);
final boolean onQuickSettings = currentScene.equals(Scenes.QuickSettings);
final boolean onShade = currentScene.equals(Scenes.Shade);
@@ -765,6 +766,8 @@ public class StatusBarStateControllerImpl implements
// We get here if deviceUnlockStatus.isUnlocked is false but we are no longer on or over
// a keyguardish scene; we want to return SHADE_LOCKED until isUnlocked is also true.
newState = StatusBarState.SHADE_LOCKED;
+ } else if (onDream) {
+ newState = StatusBarState.SHADE_LOCKED;
} else {
throw new IllegalArgumentException(
"unhandled input to calculateStateFromSceneFramework: " + inputLogString);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt
index 9004e5d12663..aeeb0427d24b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt
@@ -22,7 +22,7 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.DisableFlagsRepositoryLog
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
-import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/domain/interactor/DisableFlagsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/domain/interactor/DisableFlagsInteractor.kt
new file mode 100644
index 000000000000..4f1b97841fde
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/domain/interactor/DisableFlagsInteractor.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.statusbar.disableflags.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+@SysUISingleton
+class DisableFlagsInteractor @Inject constructor(repository: DisableFlagsRepository) {
+ /** A model of the disable flags last received from [IStatusBar]. */
+ val disableFlags: StateFlow<DisableFlagsModel> = repository.disableFlags
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/shared/model/DisableFlagsModel.kt
index ce25cf5895c1..6507237cfc24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/shared/model/DisableFlagsModel.kt
@@ -1,18 +1,20 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
+ * 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.
+ * 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.disableflags.data.model
+package com.android.systemui.statusbar.disableflags.shared.model
import android.app.StatusBarManager.DISABLE2_NONE
import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
index 8079ce540e1b..4ea597a12ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
@@ -17,17 +17,15 @@
package com.android.systemui.statusbar.notification.domain.interactor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.domain.interactor.DisableFlagsInteractor
import javax.inject.Inject
/** Interactor for notification alerting. */
@SysUISingleton
class NotificationAlertsInteractor
@Inject
-constructor(
- private val disableFlagsRepository: DisableFlagsRepository,
-) {
+constructor(private val disableFlagsInteractor: DisableFlagsInteractor) {
/** Returns true if notification alerts are allowed. */
fun areNotificationAlertsEnabled(): Boolean =
- disableFlagsRepository.disableFlags.value.areNotificationAlertsEnabled()
+ disableFlagsInteractor.disableFlags.value.areNotificationAlertsEnabled()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 08d177f933c1..d1de6be27296 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1543,7 +1543,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
setDragController(null);
mGroupParentWhenDismissed = mNotificationParent;
mChildAfterViewWhenDismissed = null;
- mEntry.getIcons().getStatusBarIcon().setDismissed();
if (isChildInGroup()) {
List<ExpandableNotificationRow> notificationChildren =
mNotificationParent.getAttachedChildren();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 9166e7e9bed2..786d7d9ea0f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -573,8 +573,29 @@ public class NotificationContentView extends FrameLayout implements Notification
return;
}
- mUiEventLogger.log(
- NotificationCompactHeadsUpEvent.NOTIFICATION_COMPACT_HUN_SHOWN);
+ final StatusBarNotification containingRowSbn = getContainingRowSbn();
+ if (containingRowSbn == null) {
+ return;
+ }
+
+ mUiEventLogger.logWithInstanceId(
+ NotificationCompactHeadsUpEvent.NOTIFICATION_COMPACT_HUN_SHOWN,
+ containingRowSbn.getUid(),
+ containingRowSbn.getPackageName(),
+ containingRowSbn.getInstanceId());
+ }
+
+ @Nullable
+ private StatusBarNotification getContainingRowSbn() {
+ if (mContainingNotification == null) {
+ return null;
+ }
+ final NotificationEntry entry = mContainingNotification.getEntry();
+ if (entry == null) {
+ return null;
+ }
+
+ return entry.getSbn();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index db294934c9ed..80c8e8b2a109 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -171,12 +171,14 @@ import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionListener;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeLogger;
import com.android.systemui.shade.ShadeSurface;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.AutoHideUiElement;
@@ -366,6 +368,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private PhoneStatusBarViewController mPhoneStatusBarViewController;
private PhoneStatusBarTransitions mStatusBarTransitions;
+ private final Provider<StatusBarLongPressGestureDetector> mStatusBarLongPressGestureDetector;
private final AuthRippleController mAuthRippleController;
@WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -671,6 +674,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
ShadeController shadeController,
WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ Provider<StatusBarLongPressGestureDetector> statusBarLongPressGestureDetector,
ViewMediatorCallback viewMediatorCallback,
InitController initController,
@Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
@@ -778,6 +782,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mShadeController = shadeController;
mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
mKeyguardViewMediatorCallback = viewMediatorCallback;
mInitController = initController;
mPluginDependencyProvider = pluginDependencyProvider;
@@ -1527,6 +1532,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// to touch outside the customizer to close it, such as on the status or nav bar.
mShadeController.onStatusBarTouch(event);
}
+ if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+ && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mStatusBarLongPressGestureDetector.get().handleTouch(event);
+ }
+
return getNotificationShadeWindowView().onTouchEvent(event);
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 91c43ddf1ce4..176dd8de6cd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -39,8 +39,8 @@ import com.android.systemui.Dependency;
import com.android.systemui.Flags;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.res.R;
-import com.android.systemui.shade.LongPressGestureDetector;
import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
@@ -69,7 +69,7 @@ public class PhoneStatusBarView extends FrameLayout {
private InsetsFetcher mInsetsFetcher;
private int mDensity;
private float mFontScale;
- private LongPressGestureDetector mLongPressGestureDetector;
+ private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -81,9 +81,10 @@ public class PhoneStatusBarView extends FrameLayout {
mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class);
}
- void setLongPressGestureDetector(LongPressGestureDetector longPressGestureDetector) {
+ void setLongPressGestureDetector(
+ StatusBarLongPressGestureDetector statusBarLongPressGestureDetector) {
if (ShadeExpandsOnStatusBarLongPress.isEnabled()) {
- mLongPressGestureDetector = longPressGestureDetector;
+ mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
}
}
@@ -207,8 +208,9 @@ public class PhoneStatusBarView extends FrameLayout {
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (ShadeExpandsOnStatusBarLongPress.isEnabled() && mLongPressGestureDetector != null) {
- mLongPressGestureDetector.handleTouch(event);
+ if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+ && mStatusBarLongPressGestureDetector != null) {
+ mStatusBarLongPressGestureDetector.handleTouch(event);
}
if (mTouchEventHandler == null) {
Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index a94db490df0c..16e023ce17fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -34,11 +34,11 @@ import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
@@ -69,7 +69,7 @@ private constructor(
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
private val panelExpansionInteractor: PanelExpansionInteractor,
- private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+ private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -119,7 +119,7 @@ private constructor(
addCursorSupportToIconContainers()
if (ShadeExpandsOnStatusBarLongPress.isEnabled) {
- mView.setLongPressGestureDetector(longPressGestureDetector.get())
+ mView.setLongPressGestureDetector(statusBarLongPressGestureDetector.get())
}
progressProvider?.setReadyToHandleTransition(true)
@@ -336,7 +336,7 @@ private constructor(
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
private val panelExpansionInteractor: PanelExpansionInteractor,
- private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+ private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
@@ -361,7 +361,7 @@ private constructor(
shadeController,
shadeViewController,
panelExpansionInteractor,
- longPressGestureDetector,
+ statusBarLongPressGestureDetector,
windowRootView,
shadeLogger,
statusBarMoveFromCenterAnimationController,
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 13ac321473f4..f3d513940bcf 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
@@ -113,34 +113,38 @@ constructor(
}
private val showIcon =
- canShowIcon
- .flatMapLatest { canShow ->
- if (!canShow) {
- flowOf(false)
- } else {
- combine(
- shouldShowIconForOosAfterHysteresis,
- interactor.connectionState,
- interactor.isWifiActive,
- airplaneModeRepository.isAirplaneMode,
- ) { showForOos, connectionState, isWifiActive, isAirplaneMode ->
- if (isWifiActive || isAirplaneMode) {
- false
- } else {
- showForOos ||
- connectionState == SatelliteConnectionState.On ||
- connectionState == SatelliteConnectionState.Connected
+ if (interactor.isOpportunisticSatelliteIconEnabled) {
+ canShowIcon
+ .flatMapLatest { canShow ->
+ if (!canShow) {
+ flowOf(false)
+ } else {
+ combine(
+ shouldShowIconForOosAfterHysteresis,
+ interactor.connectionState,
+ interactor.isWifiActive,
+ airplaneModeRepository.isAirplaneMode,
+ ) { showForOos, connectionState, isWifiActive, isAirplaneMode ->
+ if (isWifiActive || isAirplaneMode) {
+ false
+ } else {
+ showForOos ||
+ connectionState == SatelliteConnectionState.On ||
+ connectionState == SatelliteConnectionState.Connected
+ }
}
}
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "vm",
+ columnName = COL_VISIBLE,
+ initialValue = false,
+ )
+ } else {
+ flowOf(false)
}
- .distinctUntilChanged()
- .logDiffsForTable(
- tableLog,
- columnPrefix = "vm",
- columnName = COL_VISIBLE,
- initialValue = false,
- )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val icon: StateFlow<Icon?> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt
index 9164da721e3a..b2a0272c06d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.pipeline.shared.domain.interactor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.domain.interactor.DisableFlagsInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.model.StatusBarDisableFlagsVisibilityModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -30,13 +30,13 @@ import kotlinx.coroutines.flow.map
@SysUISingleton
class CollapsedStatusBarInteractor
@Inject
-constructor(disableFlagsRepository: DisableFlagsRepository) {
+constructor(DisableFlagsInteractor: DisableFlagsInteractor) {
/**
* The visibilities of various status bar child views, based only on the information we received
* from disable flags.
*/
val visibilityViaDisableFlags: Flow<StatusBarDisableFlagsVisibilityModel> =
- disableFlagsRepository.disableFlags.map {
+ DisableFlagsInteractor.disableFlags.map {
StatusBarDisableFlagsVisibilityModel(
isClockAllowed = it.isClockEnabled,
areNotificationIconsAllowed = it.areNotificationIconsEnabled,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index c7b6be3fc4ac..a72c83e9579c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -92,8 +92,7 @@ constructor(
private var wifiPickerTracker: WifiPickerTracker? = null
- @VisibleForTesting
- val selectedUserContext: Flow<Context> =
+ private val selectedUserContext: Flow<Context> =
userRepository.selectedUserInfo.map {
applicationContext.createContextAsUser(UserHandle.of(it.id), /* flags= */ 0)
}
@@ -112,8 +111,7 @@ constructor(
selectedUserContext
.flatMapLatest { currentContext
-> // flatMapLatest because when selectedUserContext emits a new value, we want
- // to
- // re-create a whole flow
+ // to re-create a whole flow
callbackFlow {
val callback =
object : WifiPickerTracker.WifiPickerTrackerCallback {
@@ -132,20 +130,16 @@ constructor(
.map { it.toWifiNetworkModel() }
// [WifiPickerTracker.connectedWifiEntry] will return the
- // same
- // instance but with updated internals. For example, when
- // its
- // validation status changes from false to true, the same
- // instance is re-used but with the validated field updated.
+ // same instance but with updated internals. For example,
+ // when its validation status changes from false to true,
+ // the same instance is re-used but with the validated
+ // field updated.
//
// Because it's the same instance, the flow won't re-emit
- // the
- // value (even though the internals have changed). So, we
- // need
- // to transform it into our internal model immediately.
- // [toWifiNetworkModel] always returns a new instance, so
- // the
- // flow is guaranteed to emit.
+ // the value (even though the internals have changed). So,
+ // we need to transform it into our internal model
+ // immediately. [toWifiNetworkModel] always returns a new
+ // instance, so the flow is guaranteed to emit.
send(
newPrimaryNetwork =
connectedEntry?.toPrimaryWifiNetworkModel()
@@ -235,20 +229,15 @@ constructor(
.map { it.toWifiNetworkModel() }
// [WifiPickerTracker.connectedWifiEntry] will return the same
- // instance
- // but with updated internals. For example, when its validation
- // status
- // changes from false to true, the same instance is re-used but
- // with the
- // validated field updated.
+ // instance but with updated internals. For example, when its
+ // validation status changes from false to true, the same
+ // instance is re-used but with the validated field updated.
//
// Because it's the same instance, the flow won't re-emit the
- // value
- // (even though the internals have changed). So, we need to
- // transform it
- // into our internal model immediately. [toWifiNetworkModel]
- // always
- // returns a new instance, so the flow is guaranteed to emit.
+ // value (even though the internals have changed). So, we need
+ // to transform it into our internal model immediately.
+ // [toWifiNetworkModel] always returns a new instance, so the
+ // flow is guaranteed to emit.
send(
newPrimaryNetwork =
connectedEntry?.toPrimaryWifiNetworkModel()
@@ -292,12 +281,10 @@ constructor(
.create(applicationContext, lifecycle, callback, "WifiRepository")
.apply {
// By default, [WifiPickerTracker] will scan to see all
- // available wifi
- // networks in the area. Because SysUI only needs to display the
- // **connected** network, we don't need scans to be running (and
- // in fact,
- // running scans is costly and should be avoided whenever
- // possible).
+ // available wifi networks in the area. Because SysUI only
+ // needs to display the **connected** network, we don't
+ // need scans to be running (and in fact, running scans is
+ // costly and should be avoided whenever possible).
this?.disableScanning()
}
// The lifecycle must be STARTED in order for the callback to receive
@@ -382,16 +369,11 @@ constructor(
private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel {
// WifiEntry instance values aren't guaranteed to be stable between method calls
- // because
- // WifiPickerTracker is continuously updating the same object. Save the level in a
- // local
- // variable so that checking the level validity here guarantees that the level will
- // still be
- // valid when we create the `WifiNetworkModel.Active` instance later. Otherwise, the
- // level
- // could be valid here but become invalid later, and `WifiNetworkModel.Active` will
- // throw
- // an exception. See b/362384551.
+ // because WifiPickerTracker is continuously updating the same object. Save the
+ // level in a local variable so that checking the level validity here guarantees
+ // that the level will still be valid when we create the `WifiNetworkModel.Active`
+ // instance later. Otherwise, the level could be valid here but become invalid
+ // later, and `WifiNetworkModel.Active` will throw an exception. See b/362384551.
return WifiNetworkModel.CarrierMerged.of(
subscriptionId = this.subscriptionId,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
index 584cd3b00a57..1e043ec48142 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
@@ -36,6 +36,9 @@ interface StatusBarWindowController {
/** Adds the status bar view to the window manager. */
fun attach()
+ /** Called when work should stop and resources should be released. */
+ fun stop()
+
/** Adds the given view to the status bar window view. */
fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams)
@@ -78,7 +81,7 @@ interface StatusBarWindowController {
*/
fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean)
- interface Factory {
+ fun interface Factory {
fun create(
context: Context,
viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
index 6953bbf735f1..811a2ec44ccc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
@@ -51,10 +51,12 @@ import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.DelegateTransitionAnimatorController;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
+import com.android.systemui.statusbar.core.StatusBarRootModernization;
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.window.StatusBarWindowModule.InternalWindowViewInflater;
@@ -66,6 +68,7 @@ import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
import java.util.Optional;
+import java.util.concurrent.Executor;
/**
* Encapsulates all logic for the status bar window state management.
@@ -79,6 +82,7 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController
private final StatusBarConfigurationController mStatusBarConfigurationController;
private final IWindowManager mIWindowManager;
private final StatusBarContentInsetsProvider mContentInsetsProvider;
+ private final Executor mMainExecutor;
private int mBarHeight = -1;
private final State mCurrentState = new State();
private boolean mIsAttached;
@@ -101,12 +105,14 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController
IWindowManager iWindowManager,
@Assisted StatusBarContentInsetsProvider contentInsetsProvider,
FragmentService fragmentService,
- Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) {
+ Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider,
+ @Main Executor mainExecutor) {
mContext = context;
mWindowManager = viewCaptureAwareWindowManager;
mStatusBarConfigurationController = statusBarConfigurationController;
mIWindowManager = iWindowManager;
mContentInsetsProvider = contentInsetsProvider;
+ mMainExecutor = mainExecutor;
mStatusBarWindowView = statusBarWindowViewInflater.inflate(context);
mFragmentService = fragmentService;
mLaunchAnimationContainer = mStatusBarWindowView.findViewById(
@@ -167,6 +173,19 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController
}
@Override
+ public void stop() {
+ StatusBarConnectedDisplays.assertInNewMode();
+
+ mWindowManager.removeView(mStatusBarWindowView);
+
+ if (StatusBarRootModernization.isEnabled()) {
+ return;
+ }
+ // Fragment transactions need to happen on the main thread.
+ mMainExecutor.execute(() -> mFragmentService.removeAndDestroy(mStatusBarWindowView));
+ }
+
+ @Override
public void addViewToWindow(@NonNull View view, @NonNull ViewGroup.LayoutParams layoutParams) {
mStatusBarWindowView.addView(view, layoutParams);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
index 051d463a8b97..74031612f28e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
@@ -70,6 +70,10 @@ constructor(
)
}
+ override suspend fun onDisplayRemovalAction(instance: StatusBarWindowController) {
+ instance.stop()
+ }
+
override val instanceClass = StatusBarWindowController::class.java
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index d97cae2a99e3..d367455d26c7 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -50,6 +50,7 @@ import com.android.systemui.plugins.ToastPlugin;
public class SystemUIToast implements ToastPlugin.Toast {
static final String TAG = "SystemUIToast";
final Context mContext;
+ final Context mDisplayContext;
final CharSequence mText;
final ToastPlugin.Toast mPluginToast;
@@ -68,17 +69,18 @@ public class SystemUIToast implements ToastPlugin.Toast {
@Nullable private final Animator mInAnimator;
@Nullable private final Animator mOutAnimator;
- SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
- String packageName, int userId, int orientation) {
- this(layoutInflater, context, text, null, packageName, userId,
+ SystemUIToast(LayoutInflater layoutInflater, Context applicationContext, Context displayContext,
+ CharSequence text, String packageName, int userId, int orientation) {
+ this(layoutInflater, applicationContext, displayContext, text, null, packageName, userId,
orientation);
}
- SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
- ToastPlugin.Toast pluginToast, String packageName, @UserIdInt int userId,
- int orientation) {
+ SystemUIToast(LayoutInflater layoutInflater, Context applicationContext, Context displayContext,
+ CharSequence text, ToastPlugin.Toast pluginToast, String packageName,
+ @UserIdInt int userId, int orientation) {
mLayoutInflater = layoutInflater;
- mContext = context;
+ mContext = applicationContext;
+ mDisplayContext = displayContext;
mText = text;
mPluginToast = pluginToast;
mPackageName = packageName;
@@ -221,9 +223,9 @@ public class SystemUIToast implements ToastPlugin.Toast {
mPluginToast.onOrientationChange(orientation);
}
- mDefaultY = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
+ mDefaultY = mDisplayContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
mDefaultGravity =
- mContext.getResources().getInteger(R.integer.config_toastDefaultGravity);
+ mDisplayContext.getResources().getInteger(R.integer.config_toastDefaultGravity);
}
private Animator createInAnimator() {
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
index 9ae66749aa0a..388d4bd6780b 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -65,15 +65,16 @@ public class ToastFactory implements Dumpable {
/**
* Create a toast to be shown by ToastUI.
*/
- public SystemUIToast createToast(Context context, CharSequence text, String packageName,
- int userId, int orientation) {
- LayoutInflater layoutInflater = LayoutInflater.from(context);
+ public SystemUIToast createToast(Context applicationContext, Context displayContext,
+ CharSequence text, String packageName, int userId, int orientation) {
+ LayoutInflater layoutInflater = LayoutInflater.from(displayContext);
if (isPluginAvailable()) {
- return new SystemUIToast(layoutInflater, context, text, mPlugin.createToast(text,
- packageName, userId), packageName, userId, orientation);
+ return new SystemUIToast(layoutInflater, applicationContext, displayContext, text,
+ mPlugin.createToast(text, packageName, userId), packageName, userId,
+ orientation);
}
- return new SystemUIToast(layoutInflater, context, text, packageName, userId,
- orientation);
+ return new SystemUIToast(layoutInflater, applicationContext, displayContext, text,
+ packageName, userId, orientation);
}
private boolean isPluginAvailable() {
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 32a4f12777ac..12f73b8a887d 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -135,8 +135,8 @@ public class ToastUI implements
return;
}
Context displayContext = context.createDisplayContext(display);
- mToast = mToastFactory.createToast(displayContext /* sysuiContext */, text, packageName,
- userHandle.getIdentifier(), mOrientation);
+ mToast = mToastFactory.createToast(mContext, displayContext /* sysuiContext */, text,
+ packageName, userHandle.getIdentifier(), mOrientation);
if (mToast.getInAnimation() != null) {
mToast.getInAnimation().start();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index 6816d35f7699..c4b028d7d98b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -49,7 +49,7 @@ constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Fact
val drawerContainer = requireViewById<View>(R.id.volume_drawer_container)
val selectedButtonView =
requireViewById<ImageButton>(R.id.volume_new_ringer_active_button)
- val volumeDialogView = requireViewById<ViewGroup>(R.id.volume_dialog)
+ val volumeDialogBackgroundView = requireViewById<View>(R.id.volume_dialog_background)
repeatWhenAttached {
viewModel(
traceName = "VolumeDialogRingerViewBinder",
@@ -71,19 +71,17 @@ constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Fact
is RingerDrawerState.Initial -> {
drawerContainer.visibility = View.GONE
selectedButtonView.visibility = View.VISIBLE
- volumeDialogView.setBackgroundResource(
+ volumeDialogBackgroundView.setBackgroundResource(
R.drawable.volume_dialog_background
)
}
-
is RingerDrawerState.Closed -> {
drawerContainer.visibility = View.GONE
selectedButtonView.visibility = View.VISIBLE
- volumeDialogView.setBackgroundResource(
+ volumeDialogBackgroundView.setBackgroundResource(
R.drawable.volume_dialog_background
)
}
-
is RingerDrawerState.Open -> {
drawerContainer.visibility = View.VISIBLE
selectedButtonView.visibility = View.GONE
@@ -91,17 +89,16 @@ constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Fact
uiModel.currentButtonIndex !=
uiModel.availableButtons.size - 1
) {
- volumeDialogView.setBackgroundResource(
+ volumeDialogBackgroundView.setBackgroundResource(
R.drawable.volume_dialog_background_small_radius
)
}
}
}
}
-
is RingerViewModelState.Unavailable -> {
drawerAndRingerContainer.visibility = View.GONE
- volumeDialogView.setBackgroundResource(
+ volumeDialogBackgroundView.setBackgroundResource(
R.drawable.volume_dialog_background
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index a17c1e541b5e..9078f82d3e98 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -38,9 +38,10 @@ constructor(private val viewModelFactory: VolumeDialogSlidersViewModel.Factory)
fun bind(view: View) {
with(view) {
- val volumeDialog: View = requireViewById(R.id.volume_dialog)
val floatingSlidersContainer: ViewGroup =
requireViewById(R.id.volume_dialog_floating_sliders_container)
+ val mainSliderContainer: View =
+ requireViewById(R.id.volume_dialog_main_slider_container)
repeatWhenAttached {
viewModel(
traceName = "VolumeDialogSlidersViewBinder",
@@ -49,7 +50,7 @@ constructor(private val viewModelFactory: VolumeDialogSlidersViewModel.Factory)
) { viewModel ->
viewModel.sliders
.onEach { uiModel ->
- uiModel.sliderComponent.sliderViewBinder().bind(volumeDialog)
+ uiModel.sliderComponent.sliderViewBinder().bind(mainSliderContainer)
val floatingSliderViewBinders = uiModel.floatingSliderComponent
floatingSlidersContainer.ensureChildCount(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index d9a945cfedfd..f6c1743a4bea 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -19,13 +19,11 @@ package com.android.systemui.volume.dialog.ui.binder
import android.app.Dialog
import android.graphics.Rect
import android.graphics.Region
-import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.ViewTreeObserver.InternalInsetsInfo
-import android.widget.FrameLayout
-import androidx.annotation.GravityInt
+import androidx.constraintlayout.motion.widget.MotionLayout
import com.android.internal.view.RotationPolicy
import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -41,7 +39,6 @@ import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSlidersViewBind
import com.android.systemui.volume.dialog.ui.VolumeDialogResources
import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
import com.android.systemui.volume.dialog.ui.utils.suspendAnimate
-import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogGravityViewModel
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel
import com.android.systemui.volume.dialog.utils.VolumeTracer
import javax.inject.Inject
@@ -53,6 +50,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -63,7 +61,6 @@ class VolumeDialogViewBinder
@Inject
constructor(
private val volumeResources: VolumeDialogResources,
- private val gravityViewModel: VolumeDialogGravityViewModel,
private val dialogViewModelFactory: VolumeDialogViewModel.Factory,
private val jankListenerFactory: JankListenerFactory,
private val tracer: VolumeTracer,
@@ -74,21 +71,23 @@ constructor(
fun bind(dialog: Dialog) {
// Root view of the Volume Dialog.
- val root: ViewGroup = dialog.requireViewById(R.id.volume_dialog_root)
- // Volume Dialog container view that contains the dialog itself without the floating sliders
- val container: View = root.requireViewById(R.id.volume_dialog_container)
- container.alpha = 0f
- container.repeatWhenAttached {
+ val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog_root)
+ root.alpha = 0f
+ root.repeatWhenAttached {
root.viewModel(
traceName = "VolumeDialogViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
factory = { dialogViewModelFactory.create() },
) { viewModel ->
- animateVisibility(container, dialog, viewModel.dialogVisibilityModel)
+ animateVisibility(root, dialog, viewModel.dialogVisibilityModel)
viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this)
- gravityViewModel.dialogGravity
- .onEach { container.setLayoutGravity(it) }
+ viewModel.motionState
+ .scan(0) { acc, motionState ->
+ // don't animate the initial state
+ root.transitionToState(motionState, animate = acc != 0)
+ acc + 1
+ }
.launchIn(this)
launch { root.viewTreeObserver.computeInternalInsetsListener(root) }
@@ -130,15 +129,13 @@ constructor(
.launchIn(this)
}
- private suspend fun calculateTranslationX(view: View): Float? {
+ private fun calculateTranslationX(view: View): Float? {
return if (view.display.rotation == RotationPolicy.NATURAL_ROTATION) {
- val dialogGravity = gravityViewModel.dialogGravity.first()
- val isGravityLeft = (dialogGravity and Gravity.LEFT) == Gravity.LEFT
- if (isGravityLeft) {
+ if (view.isLayoutRtl) {
-1
} else {
1
- } * view.width / 2.0f
+ } * view.width / 2f
} else {
null
}
@@ -211,10 +208,11 @@ constructor(
getBoundsInWindow(boundsRect, false)
}
- private fun View.setLayoutGravity(@GravityInt newGravity: Int) {
- val frameLayoutParams =
- layoutParams as? FrameLayout.LayoutParams
- ?: error("View must be a child of a FrameLayout")
- layoutParams = frameLayoutParams.apply { gravity = newGravity }
+ private fun MotionLayout.transitionToState(newState: Int, animate: Boolean) {
+ if (animate) {
+ transitionToState(newState)
+ } else {
+ jumpToState(newState)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
deleted file mode 100644
index 112afb1debf5..000000000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.dialog.ui.viewmodel
-
-import android.content.Context
-import android.content.res.Configuration
-import android.view.Gravity
-import androidx.annotation.GravityInt
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.UiBackground
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.DevicePostureController
-import com.android.systemui.statusbar.policy.devicePosture
-import com.android.systemui.statusbar.policy.onConfigChanged
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
-import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
-
-/** Exposes dialog [GravityInt] for use in the UI layer. */
-@VolumeDialogScope
-class VolumeDialogGravityViewModel
-@Inject
-constructor(
- @Application private val context: Context,
- @VolumeDialog private val coroutineScope: CoroutineScope,
- @UiBackground private val uiBackgroundCoroutineContext: CoroutineContext,
- configurationController: ConfigurationController,
- private val devicePostureController: DevicePostureController,
-) {
-
- @GravityInt private var originalGravity: Int = context.getAbsoluteGravity()
-
- val dialogGravity: Flow<Int> =
- combine(
- devicePostureController.devicePosture(),
- configurationController.onConfigChanged.onEach { onConfigurationChanged() },
- ) { devicePosture, configuration ->
- context.calculateGravity(devicePosture, configuration)
- }
- .stateIn(
- scope = coroutineScope,
- started = SharingStarted.Eagerly,
- context.calculateGravity(),
- )
-
- private suspend fun onConfigurationChanged() {
- withContext(uiBackgroundCoroutineContext) { originalGravity = context.getAbsoluteGravity() }
- }
-
- @GravityInt
- private fun Context.calculateGravity(
- devicePosture: Int = devicePostureController.devicePosture,
- config: Configuration = resources.configuration,
- ): Int {
- val isLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE
- val isHalfOpen = devicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED
- val gravity =
- if (isLandscape && isHalfOpen) {
- originalGravity or Gravity.TOP
- } else {
- originalGravity
- }
- return getAbsoluteGravity(gravity)
- }
-}
-
-@GravityInt
-private fun Context.getAbsoluteGravity(
- gravity: Int = resources.getInteger(R.integer.volume_dialog_gravity)
-): Int = with(resources) { Gravity.getAbsoluteGravity(gravity, configuration.layoutDirection) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
index 869a6a2e87d5..0352799916bc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
@@ -17,7 +17,12 @@
package com.android.systemui.volume.dialog.ui.viewmodel
import android.content.Context
+import android.content.res.Configuration
import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.devicePosture
+import com.android.systemui.statusbar.policy.onConfigChanged
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
@@ -32,6 +37,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
/** Provides a state for the Volume Dialog. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -42,8 +48,23 @@ constructor(
dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor,
volumeDialogStateInteractor: VolumeDialogStateInteractor,
+ devicePostureController: DevicePostureController,
+ configurationController: ConfigurationController,
) {
+ val motionState: Flow<Int> =
+ combine(
+ devicePostureController.devicePosture(),
+ configurationController.onConfigChanged.onStart {
+ emit(context.resources.configuration)
+ },
+ ) { devicePosture, configuration ->
+ if (shouldOffsetVolumeDialog(devicePosture, configuration)) {
+ R.id.volume_dialog_half_folded_constraint_set
+ } else {
+ R.id.volume_dialog_constraint_set
+ }
+ }
val dialogVisibilityModel: Flow<VolumeDialogVisibilityModel> =
dialogVisibilityInteractor.dialogVisibility
val dialogTitle: Flow<String> =
@@ -57,6 +78,13 @@ constructor(
}
.filterNotNull()
+ /** @return true when the foldable device screen curve is in the way of the volume dialog */
+ private fun shouldOffsetVolumeDialog(devicePosture: Int, config: Configuration): Boolean {
+ val isLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE
+ val isHalfOpen = devicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+ return isLandscape && isHalfOpen
+ }
+
@AssistedFactory
interface Factory {
fun create(): VolumeDialogViewModel
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 65b62737b692..2aa6e7b18154 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -105,8 +105,7 @@ class ClockEventControllerTest : SysuiTestCase() {
private val mainExecutor = ImmediateExecutor()
private lateinit var repository: FakeKeyguardRepository
- private val messageBuffer = LogcatOnlyMessageBuffer(LogLevel.DEBUG)
- private val clockBuffers = ClockMessageBuffers(messageBuffer, messageBuffer, messageBuffer)
+ private val clockBuffers = ClockMessageBuffers(LogcatOnlyMessageBuffer(LogLevel.DEBUG))
private lateinit var underTest: ClockEventController
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index a940bc9b3e20..425aad2bd43c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -16,10 +16,12 @@ import android.view.RemoteAnimationAdapter
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
import android.view.ViewGroup
+import android.view.WindowManager.TRANSIT_NONE
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.window.RemoteTransition
import android.window.TransitionFilter
+import android.window.WindowAnimationState
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -34,6 +36,10 @@ import junit.framework.Assert.assertTrue
import junit.framework.AssertionFailedError
import kotlin.concurrent.thread
import kotlin.test.assertEquals
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
@@ -258,7 +264,6 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
@Test
fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() {
-
val controller =
object : DelegateTransitionAnimatorController(controller) {
override val transitionCookie =
@@ -273,7 +278,6 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
@Test
fun doesNotRegisterLongLivedTransitionIfMissingRequiredProperties() {
-
// No TransitionCookie
val controllerWithoutCookie =
object : DelegateTransitionAnimatorController(controller) {
@@ -348,7 +352,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
fun doesNotStartIfAnimationIsCancelled() {
val runner = activityTransitionAnimator.createRunner(controller)
runner.onAnimationCancelled()
- runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
+ runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback)
waitForIdleSync()
verify(controller).onTransitionAnimationCancelled()
@@ -361,7 +365,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@Test
fun cancelsIfNoOpeningWindowIsFound() {
val runner = activityTransitionAnimator.createRunner(controller)
- runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
+ runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback)
waitForIdleSync()
verify(controller).onTransitionAnimationCancelled()
@@ -374,7 +378,13 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@Test
fun startsAnimationIfWindowIsOpening() {
val runner = activityTransitionAnimator.createRunner(controller)
- runner.onAnimationStart(0, arrayOf(fakeWindow()), emptyArray(), emptyArray(), iCallback)
+ runner.onAnimationStart(
+ TRANSIT_NONE,
+ arrayOf(fakeWindow()),
+ emptyArray(),
+ emptyArray(),
+ iCallback,
+ )
waitForIdleSync()
verify(listener).onTransitionAnimationStart()
verify(controller).onTransitionAnimationStart(anyBoolean())
@@ -387,6 +397,113 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
}
}
+ @DisableFlags(
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ )
+ @Test
+ fun creatingRunnerWithLazyInitializationThrows_whenTheFlagsAreDisabled() {
+ assertThrows(IllegalStateException::class.java) {
+ activityTransitionAnimator.createRunner(controller, initializeLazily = true)
+ }
+ }
+
+ @EnableFlags(
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ )
+ @Test
+ fun runnerCreatesDelegateLazily_whenPostingTimeouts() {
+ val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = true)
+ assertNull(runner.delegate)
+ runner.postTimeouts()
+ assertNotNull(runner.delegate)
+ }
+
+ @EnableFlags(
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ )
+ @Test
+ fun runnerCreatesDelegateLazily_onAnimationStart() {
+ val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = true)
+ assertNull(runner.delegate)
+
+ // The delegate is cleaned up after execution (which happens in another thread), so what we
+ // do instead is check if it becomes non-null at any point with a 1 second timeout. This
+ // will tell us that takeOverWithAnimation() triggered the lazy initialization.
+ var delegateInitialized = false
+ runBlocking {
+ val initChecker = launch {
+ withTimeout(1.seconds) {
+ while (runner.delegate == null) continue
+ delegateInitialized = true
+ }
+ }
+ runner.onAnimationStart(
+ TRANSIT_NONE,
+ arrayOf(fakeWindow()),
+ emptyArray(),
+ emptyArray(),
+ iCallback,
+ )
+ initChecker.join()
+ }
+ assertTrue(delegateInitialized)
+ }
+
+ @EnableFlags(
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ )
+ @Test
+ fun runnerCreatesDelegateLazily_onAnimationTakeover() {
+ val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = true)
+ assertNull(runner.delegate)
+
+ // The delegate is cleaned up after execution (which happens in another thread), so what we
+ // do instead is check if it becomes non-null at any point with a 1 second timeout. This
+ // will tell us that takeOverWithAnimation() triggered the lazy initialization.
+ var delegateInitialized = false
+ runBlocking {
+ val initChecker = launch {
+ withTimeout(1.seconds) {
+ while (runner.delegate == null) continue
+ delegateInitialized = true
+ }
+ }
+ runner.takeOverAnimation(
+ arrayOf(fakeWindow()),
+ arrayOf(WindowAnimationState()),
+ SurfaceControl.Transaction(),
+ iCallback,
+ )
+ initChecker.join()
+ }
+ assertTrue(delegateInitialized)
+ }
+
+ @DisableFlags(
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ )
+ @Test
+ fun animationTakeoverThrows_whenTheFlagsAreDisabled() {
+ val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = false)
+ assertThrows(IllegalStateException::class.java) {
+ runner.takeOverAnimation(
+ arrayOf(fakeWindow()),
+ emptyArray(),
+ SurfaceControl.Transaction(),
+ iCallback,
+ )
+ }
+ }
+
+ @DisableFlags(
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ )
@Test
fun disposeRunner_delegateDereferenced() {
val runner = activityTransitionAnimator.createRunner(controller)
@@ -409,7 +526,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
false,
Rect(),
Rect(),
- 0,
+ 1,
Point(),
Rect(),
bounds,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index 0b9c06f2dbe2..5ada2f3fd63d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -222,7 +222,7 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase {
when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(SUB_ID);
SubscriptionInfo info = mock(SubscriptionInfo.class);
when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info);
- when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt()))
+ when(mToastFactory.createToast(any(), any(), anyString(), anyString(), anyInt(), anyInt()))
.thenReturn(mSystemUIToast);
when(mSystemUIToast.getView()).thenReturn(mToastView);
when(mSystemUIToast.getGravity()).thenReturn(GRAVITY_FLAGS);
@@ -275,8 +275,8 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase {
mInternetDialogController.connectCarrierNetwork();
verify(mMergedCarrierEntry).connect(null /* callback */, false /* showToast */);
- verify(mToastFactory).createToast(any(), eq(TOAST_MESSAGE_STRING), anyString(), anyInt(),
- anyInt());
+ verify(mToastFactory).createToast(any(), any(), eq(TOAST_MESSAGE_STRING), anyString(),
+ anyInt(), anyInt());
}
@Test
@@ -288,7 +288,7 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase {
mInternetDialogController.connectCarrierNetwork();
verify(mMergedCarrierEntry, never()).connect(null /* callback */, false /* showToast */);
- verify(mToastFactory, never()).createToast(any(), anyString(), anyString(), anyInt(),
+ verify(mToastFactory, never()).createToast(any(), any(), anyString(), anyString(), anyInt(),
anyInt());
}
@@ -302,7 +302,7 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase {
mInternetDialogController.connectCarrierNetwork();
verify(mMergedCarrierEntry, never()).connect(null /* callback */, false /* showToast */);
- verify(mToastFactory, never()).createToast(any(), anyString(), anyString(), anyInt(),
+ verify(mToastFactory, never()).createToast(any(), any(), anyString(), anyString(), anyInt(),
anyInt());
}
@@ -321,7 +321,7 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase {
mInternetDialogController.connectCarrierNetwork();
verify(mMergedCarrierEntry, never()).connect(null /* callback */, false /* showToast */);
- verify(mToastFactory, never()).createToast(any(), anyString(), anyString(), anyInt(),
+ verify(mToastFactory, never()).createToast(any(), any(), anyString(), anyString(), anyInt(),
anyInt());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt
index 8bd8b72e5527..2812bd334b0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt
@@ -15,11 +15,16 @@
*/
package systemui.shared.clocks.view
+import android.graphics.Typeface
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
+import com.android.systemui.plugins.clocks.ClockSettings
+import com.android.systemui.shared.clocks.ClockContext
import com.android.systemui.shared.clocks.FontTextStyle
import com.android.systemui.shared.clocks.LogUtil
+import com.android.systemui.shared.clocks.TypefaceCache
import com.android.systemui.shared.clocks.view.SimpleDigitalClockTextView
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -38,7 +43,23 @@ class SimpleDigitalClockTextViewTest : SysuiTestCase() {
@Before
fun setup() {
- underTest = SimpleDigitalClockTextView(context, messageBuffer)
+ underTest =
+ SimpleDigitalClockTextView(
+ ClockContext(
+ context,
+ context.resources,
+ ClockSettings(),
+ TypefaceCache(messageBuffer) {
+ // TODO(b/364680873): Move constant to config_clockFontFamily when shipping
+ return@TypefaceCache Typeface.create(
+ "google-sans-flex-clock",
+ Typeface.NORMAL,
+ )
+ },
+ ClockMessageBuffers(messageBuffer),
+ messageBuffer,
+ )
+ )
underTest.textStyle = FontTextStyle()
underTest.aodStyle = FontTextStyle()
underTest.text = "0"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index a8618eb544d4..3a46d038f946 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -20,9 +20,8 @@ import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
-import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
@@ -31,7 +30,6 @@ import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
-import com.android.systemui.statusbar.policy.configurationController
import com.android.systemui.statusbar.policy.fakeConfigurationController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -144,7 +142,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
context = context,
configurationController = configurationController,
dumpManager = mock(),
- splitShadeStateController = ResourcesSplitShadeStateController()
+ splitShadeStateController = ResourcesSplitShadeStateController(),
),
keyguardTransitionControllerFactory = { notificationPanelController ->
LockscreenShadeKeyguardTransitionController(
@@ -153,7 +151,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
context = context,
configurationController = configurationController,
dumpManager = mock(),
- splitShadeStateController = ResourcesSplitShadeStateController()
+ splitShadeStateController = ResourcesSplitShadeStateController(),
)
},
depthController = depthController,
@@ -171,7 +169,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
splitShadeStateController = ResourcesSplitShadeStateController(),
shadeLockscreenInteractorLazy = { shadeLockscreenInteractor },
naturalScrollingSettingObserver = naturalScrollingSettingObserver,
- lazyQSSceneAdapter = { qsSceneAdapter }
+ lazyQSSceneAdapter = { qsSceneAdapter },
)
transitionController.addCallback(transitionControllerCallback)
@@ -229,7 +227,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
assertFalse(
"Waking to shade locked when not dozing",
- transitionController.isWakingToShadeLocked
+ transitionController.isWakingToShadeLocked,
)
}
@@ -247,9 +245,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
fun testDontGoWhenShadeDisabled() =
testScope.runTest {
disableFlagsRepository.disableFlags.value =
- DisableFlagsModel(
- disable2 = DISABLE2_NOTIFICATION_SHADE,
- )
+ DisableFlagsModel(disable2 = DISABLE2_NOTIFICATION_SHADE)
testScope.runCurrent()
transitionController.goToLockedShade(null)
verify(statusbarStateController, never()).setState(anyInt())
@@ -454,7 +450,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
val distance = 10
context.orCreateTestableResources.addOverride(
R.dimen.lockscreen_shade_scrim_transition_distance,
- distance
+ distance,
)
configurationController.notifyConfigurationChanged()
@@ -463,7 +459,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
verify(scrimController)
.transitionToFullShadeProgress(
progress = eq(0.5f),
- lockScreenNotificationsProgress = anyFloat()
+ lockScreenNotificationsProgress = anyFloat(),
)
}
@@ -474,11 +470,11 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
val delay = 10
context.orCreateTestableResources.addOverride(
R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
- distance
+ distance,
)
context.orCreateTestableResources.addOverride(
R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
- delay
+ delay,
)
configurationController.notifyConfigurationChanged()
@@ -487,7 +483,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
verify(scrimController)
.transitionToFullShadeProgress(
progress = anyFloat(),
- lockScreenNotificationsProgress = eq(0.1f)
+ lockScreenNotificationsProgress = eq(0.1f),
)
}
@@ -498,11 +494,11 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
val delay = 50
context.orCreateTestableResources.addOverride(
R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
- distance
+ distance,
)
context.orCreateTestableResources.addOverride(
R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
- delay
+ delay,
)
configurationController.notifyConfigurationChanged()
@@ -511,7 +507,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
verify(scrimController)
.transitionToFullShadeProgress(
progress = anyFloat(),
- lockScreenNotificationsProgress = eq(0f)
+ lockScreenNotificationsProgress = eq(0f),
)
}
@@ -522,11 +518,11 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
val delay = 50
context.orCreateTestableResources.addOverride(
R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
- distance
+ distance,
)
context.orCreateTestableResources.addOverride(
R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
- delay
+ delay,
)
configurationController.notifyConfigurationChanged()
@@ -535,7 +531,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
verify(scrimController)
.transitionToFullShadeProgress(
progress = anyFloat(),
- lockScreenNotificationsProgress = eq(1f)
+ lockScreenNotificationsProgress = eq(1f),
)
}
@@ -627,7 +623,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
*/
private fun ScrimController.transitionToFullShadeProgress(
progress: Float,
- lockScreenNotificationsProgress: Float
+ lockScreenNotificationsProgress: Float,
) {
setTransitionToFullShadeProgress(progress, lockScreenNotificationsProgress)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c9ada7e7f5ba..b142fc2deea9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -155,6 +155,7 @@ import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeLogger;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyboardShortcutListSearch;
@@ -174,7 +175,6 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
-import com.android.systemui.statusbar.core.StatusBarOrchestrator;
import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
@@ -372,7 +372,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
@Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
@Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
- @Mock private StatusBarOrchestrator mStatusBarOrchestrator;
+ @Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
@@ -607,6 +607,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mShadeController,
mWindowRootViewVisibilityInteractor,
mStatusBarKeyguardViewManager,
+ () -> mStatusBarLongPressGestureDetector,
mViewMediatorCallback,
mInitController,
new Handler(TestableLooper.get(this).getLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 638f195df00c..69efa87a9cac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -40,14 +40,13 @@ import com.android.systemui.flags.Flags
import com.android.systemui.plugins.fakeDarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
-import com.android.systemui.statusbar.data.repository.statusBarContentInsetsProviderStore
import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -98,7 +97,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Mock private lateinit var windowRootView: Provider<WindowRootView>
@Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var viewUtil: ViewUtil
- @Mock private lateinit var longPressGestureDetector: LongPressGestureDetector
+ @Mock private lateinit var mStatusBarLongPressGestureDetector: StatusBarLongPressGestureDetector
private lateinit var statusBarWindowStateController: StatusBarWindowStateController
private lateinit var view: PhoneStatusBarView
@@ -395,7 +394,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
shadeControllerImpl,
shadeViewController,
panelExpansionInteractor,
- { longPressGestureDetector },
+ { mStatusBarLongPressGestureDetector },
windowRootView,
shadeLogger,
viewUtil,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index 32469b6cfc30..ad38bbeba1a8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -23,13 +23,13 @@ import java.io.PrintWriter
class FakeFeatureFlagsClassic : FakeFeatureFlags()
+val FeatureFlagsClassic.fake
+ get() = this as FakeFeatureFlagsClassic
+
@Deprecated(
message = "Use FakeFeatureFlagsClassic instead.",
replaceWith =
- ReplaceWith(
- "FakeFeatureFlagsClassic",
- "com.android.systemui.flags.FakeFeatureFlagsClassic",
- ),
+ ReplaceWith("FakeFeatureFlagsClassic", "com.android.systemui.flags.FakeFeatureFlagsClassic"),
)
open class FakeFeatureFlags : FeatureFlagsClassic {
private val booleanFlags = mutableMapOf<String, Boolean>()
@@ -105,6 +105,7 @@ open class FakeFeatureFlags : FeatureFlagsClassic {
listener.onFlagChanged(
object : FlagListenable.FlagEvent {
override val flagName = flag.name
+
override fun requestNoRestart() {}
}
)
@@ -165,7 +166,7 @@ open class FakeFeatureFlags : FeatureFlagsClassic {
@Module(includes = [FakeFeatureFlagsClassicModule.Bindings::class])
class FakeFeatureFlagsClassicModule(
- @get:Provides val fakeFeatureFlagsClassic: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic(),
+ @get:Provides val fakeFeatureFlagsClassic: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic()
) {
constructor(
@@ -175,7 +176,9 @@ class FakeFeatureFlagsClassicModule(
@Module
interface Bindings {
@Binds fun bindFake(fake: FakeFeatureFlagsClassic): FeatureFlagsClassic
+
@Binds fun bindClassic(classic: FeatureFlagsClassic): FeatureFlags
+
@Binds fun bindFakeClassic(fake: FakeFeatureFlagsClassic): FakeFeatureFlags
}
}
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 3cd613b21f4b..3d60abf59d62 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
@@ -71,6 +71,8 @@ import com.android.systemui.shade.shadeController
import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor
import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
@@ -78,7 +80,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.heads
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.keyguardBypassController
import com.android.systemui.statusbar.phone.scrimController
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.fakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.wifiInteractor
import com.android.systemui.statusbar.policy.configurationController
@@ -126,7 +128,7 @@ class KosmosJavaAdapter() {
val keyguardStatusBarViewModel by lazy { kosmos.keyguardStatusBarViewModel }
val powerRepository by lazy { kosmos.fakePowerRepository }
val clock by lazy { kosmos.systemClock }
- val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
+ val mobileConnectionsRepository by lazy { kosmos.mobileConnectionsRepository }
val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
val statusBarStateController by lazy { kosmos.statusBarStateController }
val statusBarModePerDisplayRepository by lazy { kosmos.fakeStatusBarModePerDisplayRepository }
@@ -183,4 +185,6 @@ class KosmosJavaAdapter() {
val lockscreenToGlanceableHubTransitionViewModel by lazy {
kosmos.lockscreenToGlanceableHubTransitionViewModel
}
+ val disableFlagsInteractor by lazy { kosmos.disableFlagsInteractor }
+ val fakeDisableFlagsRepository by lazy { kosmos.fakeDisableFlagsRepository }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index 4ed491233f3c..45d5b387fea0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -33,7 +33,7 @@ import com.android.systemui.qs.panels.ui.viewmodel.mediaInRowInLandscapeViewMode
import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModelFactory
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.shade.transition.largeScreenShadeInterpolator
-import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
+import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor
import com.android.systemui.statusbar.sysuiStatusBarStateController
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -51,7 +51,7 @@ val Kosmos.qsFragmentComposeViewModelFactory by
footerActionsController,
sysuiStatusBarStateController,
deviceEntryInteractor,
- disableFlagsRepository,
+ disableFlagsInteractor,
keyguardTransitionInteractor,
largeScreenShadeInterpolator,
configurationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
index 8b124258909a..a4a63ec6ca21 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
@@ -21,6 +21,7 @@ package com.android.systemui.scene.domain.resolver
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.shared.model.SceneFamilies
@@ -34,6 +35,7 @@ val Kosmos.homeSceneFamilyResolver by
HomeSceneFamilyResolver(
applicationScope = applicationCoroutineScope,
deviceEntryInteractor = deviceEntryInteractor,
+ keyguardInteractor = keyguardInteractor,
keyguardEnabledInteractor = keyguardEnabledInteractor,
)
}
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 39f58aea82ef..af6d6249b4a8 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
@@ -25,7 +25,7 @@ import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.ShadeModule
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
+import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor
import com.android.systemui.statusbar.phone.dozeParameters
import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
@@ -60,7 +60,7 @@ val Kosmos.shadeInteractorImpl by
ShadeInteractorImpl(
scope = applicationCoroutineScope,
deviceProvisioningInteractor = deviceProvisioningInteractor,
- disableFlagsRepository = disableFlagsRepository,
+ disableFlagsInteractor = disableFlagsInteractor,
dozeParams = dozeParameters,
keyguardRepository = fakeKeyguardRepository,
keyguardTransitionInteractor = keyguardTransitionInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/data/repository/FakeDisableFlagsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/data/repository/FakeDisableFlagsRepository.kt
index 466a3eb83e95..9dbb547a434d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/data/repository/FakeDisableFlagsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/data/repository/FakeDisableFlagsRepository.kt
@@ -15,7 +15,7 @@
package com.android.systemui.statusbar.disableflags.data.repository
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
import dagger.Binds
import dagger.Module
import javax.inject.Inject
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/domain/interactor/DisableFlagsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/domain/interactor/DisableFlagsInteractorKosmos.kt
new file mode 100644
index 000000000000..7b4b047c130a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/domain/interactor/DisableFlagsInteractorKosmos.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.statusbar.disableflags.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
+
+val Kosmos.disableFlagsInteractor by Fixture {
+ DisableFlagsInteractor(repository = disableFlagsRepository)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
index d76defef3c97..99ed4f0db64d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
@@ -18,7 +18,7 @@ package com.android.systemui.statusbar.pipeline.airplane.domain.interactor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
val Kosmos.airplaneModeInteractor: AirplaneModeInteractor by
@@ -26,6 +26,6 @@ val Kosmos.airplaneModeInteractor: AirplaneModeInteractor by
AirplaneModeInteractor(
FakeAirplaneModeRepository(),
FakeConnectivityRepository(),
- fakeMobileConnectionsRepository,
+ mobileConnectionsRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index de73d3397db3..bfd46b664242 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -77,11 +77,7 @@ class FakeMobileConnectionsRepository(
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
return subIdRepos[subId]
- ?: FakeMobileConnectionRepository(
- subId,
- tableLogBuffer,
- )
- .also { subIdRepos[subId] = it }
+ ?: FakeMobileConnectionRepository(subId, tableLogBuffer).also { subIdRepos[subId] = it }
}
override val defaultDataSubRatConfig = MutableStateFlow(MobileMappings.Config())
@@ -135,3 +131,6 @@ class FakeMobileConnectionsRepository(
const val LTE_ADVANCED_PRO = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
}
}
+
+val MobileConnectionsRepository.fake
+ get() = this as FakeMobileConnectionsRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
index cd22f1dd6acc..b952d71b157e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
@@ -19,12 +19,20 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+
+val Kosmos.mobileMappingsProxy: MobileMappingsProxy by Fixture { FakeMobileMappingsProxy() }
+
+var Kosmos.mobileConnectionsRepositoryLogbufferName by Fixture { "FakeMobileConnectionsRepository" }
val Kosmos.fakeMobileConnectionsRepository by Fixture {
FakeMobileConnectionsRepository(
- tableLogBuffer = logcatTableLogBuffer(this, "FakeMobileConnectionsRepository"),
+ mobileMappings = mobileMappingsProxy,
+ tableLogBuffer = logcatTableLogBuffer(this, mobileConnectionsRepositoryLogbufferName),
)
}
-val Kosmos.mobileConnectionsRepository by
- Fixture<MobileConnectionsRepository> { fakeMobileConnectionsRepository }
+val Kosmos.mobileConnectionsRepository: MobileConnectionsRepository by Fixture {
+ fakeMobileConnectionsRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt
index 8e656cf002ce..00bfa994aabd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt
@@ -18,7 +18,5 @@ package com.android.systemui.statusbar.pipeline.shared.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.fakeConnectivityRepository: FakeConnectivityRepository by
- Kosmos.Fixture { FakeConnectivityRepository() }
val Kosmos.connectivityRepository: ConnectivityRepository by
- Kosmos.Fixture { fakeConnectivityRepository }
+ Kosmos.Fixture { FakeConnectivityRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
index 331e2fad19cb..c69d9a29a9b4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
@@ -42,10 +42,7 @@ class FakeConnectivityRepository : ConnectivityRepository {
* validated
*/
@JvmOverloads
- fun setMobileConnected(
- default: Boolean = true,
- validated: Boolean = true,
- ) {
+ fun setMobileConnected(default: Boolean = true, validated: Boolean = true) {
defaultConnections.value =
DefaultConnectionModel(
mobile = DefaultConnectionModel.Mobile(default),
@@ -55,10 +52,7 @@ class FakeConnectivityRepository : ConnectivityRepository {
/** Similar convenience method for ethernet */
@JvmOverloads
- fun setEthernetConnected(
- default: Boolean = true,
- validated: Boolean = true,
- ) {
+ fun setEthernetConnected(default: Boolean = true, validated: Boolean = true) {
defaultConnections.value =
DefaultConnectionModel(
ethernet = DefaultConnectionModel.Ethernet(default),
@@ -67,10 +61,7 @@ class FakeConnectivityRepository : ConnectivityRepository {
}
@JvmOverloads
- fun setWifiConnected(
- default: Boolean = true,
- validated: Boolean = true,
- ) {
+ fun setWifiConnected(default: Boolean = true, validated: Boolean = true) {
defaultConnections.value =
DefaultConnectionModel(
wifi = DefaultConnectionModel.Wifi(default),
@@ -78,3 +69,6 @@ class FakeConnectivityRepository : ConnectivityRepository {
)
}
}
+
+val ConnectivityRepository.fake
+ get() = this as FakeConnectivityRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorKosmos.kt
index 385a813996ff..13fde9608017 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorKosmos.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.pipeline.shared.domain.interactor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor
val Kosmos.collapsedStatusBarInteractor: CollapsedStatusBarInteractor by
- Kosmos.Fixture { CollapsedStatusBarInteractor(fakeDisableFlagsRepository) }
+ Kosmos.Fixture { CollapsedStatusBarInteractor(disableFlagsInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
index 528c9d9ec64d..a110a49ccba8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
@@ -27,6 +27,9 @@ class FakeStatusBarWindowController : StatusBarWindowController {
var isAttached = false
private set
+ var isStopped = false
+ private set
+
override val statusBarHeight: Int = 0
override fun refreshStatusBarHeight() {}
@@ -35,6 +38,10 @@ class FakeStatusBarWindowController : StatusBarWindowController {
isAttached = true
}
+ override fun stop() {
+ isStopped = true
+ }
+
override fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams) {}
override val backgroundView: View
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
index 173e909e3b3f..23f2b4221825 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
@@ -18,7 +18,8 @@ package com.android.systemui.statusbar.window
import android.content.testableContext
import android.view.windowManagerService
-import com.android.app.viewcapture.viewCaptureAwareWindowManager
+import com.android.app.viewcapture.realCaptureAwareWindowManager
+import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.fragments.fragmentService
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.phone.statusBarContentInsetsProvider
@@ -32,12 +33,13 @@ val Kosmos.statusBarWindowControllerImpl by
StatusBarWindowControllerImpl(
testableContext,
statusBarWindowViewInflater,
- viewCaptureAwareWindowManager,
+ realCaptureAwareWindowManager,
statusBarConfigurationController,
windowManagerService,
statusBarContentInsetsProvider,
fragmentService,
Optional.empty(),
+ fakeExecutor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.kt
new file mode 100644
index 000000000000..4941ceb7991d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.window
+
+import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.app.viewcapture.realCaptureAwareWindowManager
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayWindowPropertiesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.data.repository.statusBarConfigurationControllerStore
+import com.android.systemui.statusbar.data.repository.statusBarContentInsetsProviderStore
+import org.mockito.kotlin.mock
+
+val Kosmos.multiDisplayStatusBarWindowControllerStore by
+ Kosmos.Fixture {
+ MultiDisplayStatusBarWindowControllerStore(
+ backgroundApplicationScope = applicationCoroutineScope,
+ controllerFactory = { _, _, _, _ -> mock() },
+ displayWindowPropertiesRepository = displayWindowPropertiesRepository,
+ viewCaptureAwareWindowManagerFactory =
+ object : ViewCaptureAwareWindowManager.Factory {
+ override fun create(
+ windowManager: WindowManager
+ ): ViewCaptureAwareWindowManager {
+ return realCaptureAwareWindowManager
+ }
+ },
+ statusBarConfigurationControllerStore = statusBarConfigurationControllerStore,
+ statusBarContentInsetsProviderStore = statusBarContentInsetsProviderStore,
+ displayRepository = displayRepository,
+ )
+ }
diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh
index fe2269a8dc38..27c5ea1bd0d7 100755
--- a/ravenwood/scripts/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/run-ravenwood-tests.sh
@@ -33,7 +33,7 @@ include_re=""
exclude_re=""
smoke_exclude_re=""
dry_run=""
-while getopts "sx:f:dt" opt; do
+while getopts "sx:f:dtb" opt; do
case "$opt" in
s)
# Remove slow tests.
@@ -52,8 +52,13 @@ case "$opt" in
dry_run="echo"
;;
t)
+ # Redirect log to terminal
export RAVENWOOD_LOG_OUT=$(tty)
;;
+ b)
+ # Build only
+ ATEST=m
+ ;;
'?')
exit 1
;;
@@ -99,11 +104,16 @@ done
# Calculate the removed tests.
-diff="$(diff <(echo "${all_tests[@]}" | tr ' ' '\n') <(echo "${targets[@]}" | tr ' ' '\n') )"
+diff="$(diff <(echo "${all_tests[@]}" | tr ' ' '\n') <(echo "${targets[@]}" | tr ' ' '\n') | grep -v [0-9] )"
if [[ "$diff" != "" ]]; then
echo "Excluded tests:"
echo "$diff"
fi
-$dry_run ${ATEST:-atest} "${targets[@]}"
+run() {
+ echo "Running: ${@}"
+ "${@}"
+}
+
+run $dry_run ${ATEST:-atest} "${targets[@]}"
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index bd46debf12ca..b3fe5f234bc2 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -2,6 +2,13 @@ package: "android.service.autofill"
container: "system"
flag {
+ name: "autofill_w_metrics"
+ namespace: "autofill"
+ description: "Guards against new metrics definitions introduced in W"
+ bug: "342676602"
+}
+
+flag {
name: "autofill_credman_integration"
namespace: "autofill"
description: "Guards Autofill Framework against Autofill-Credman integration"
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index cd2a535aa2c5..e59bb42fd666 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -28,8 +28,11 @@ import android.app.ActivityManager;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.ViewNode;
import android.app.assist.AssistStructure.WindowNode;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
import android.content.ComponentName;
import android.content.Context;
+import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManager;
import android.metrics.LogMaker;
import android.os.UserHandle;
@@ -97,11 +100,12 @@ public final class Helper {
@UserIdInt int userId, @NonNull RemoteViews rView) {
final AtomicBoolean permissionsOk = new AtomicBoolean(true);
- rView.visitUris(uri -> {
- int uriOwnerId = android.content.ContentProvider.getUserIdFromUri(uri);
- boolean allowed = uriOwnerId == userId;
- permissionsOk.set(allowed & permissionsOk.get());
- });
+ rView.visitUris(
+ uri -> {
+ int uriOwnerId = android.content.ContentProvider.getUserIdFromUri(uri, userId);
+ boolean allowed = uriOwnerId == userId;
+ permissionsOk.set(allowed & permissionsOk.get());
+ });
return permissionsOk.get();
}
@@ -150,6 +154,47 @@ public final class Helper {
return (ok ? rView : null);
}
+ /**
+ * Checks the URI permissions of the icon in the slice, to see if the current userId is able to
+ * access it.
+ *
+ * <p>Returns null if slice contains user inaccessible icons
+ *
+ * <p>TODO: instead of returning a null Slice when the current userId cannot access an icon,
+ * return a reconstructed Slice without the icons. This is currently non-trivial since there are
+ * no public methods to generically add SliceItems to Slices
+ */
+ public static @Nullable Slice sanitizeSlice(Slice slice) {
+ if (slice == null) {
+ return null;
+ }
+
+ int userId = ActivityManager.getCurrentUser();
+
+ // Recontruct the Slice, filtering out bad icons
+ for (SliceItem sliceItem : slice.getItems()) {
+ if (!sliceItem.getFormat().equals(SliceItem.FORMAT_IMAGE)) {
+ // Not an image slice
+ continue;
+ }
+
+ Icon icon = sliceItem.getIcon();
+ if (icon.getType() != Icon.TYPE_URI
+ && icon.getType() != Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+ // No URIs to sanitize
+ continue;
+ }
+
+ int iconUriId = android.content.ContentProvider.getUserIdFromUri(icon.getUri(), userId);
+
+ if (iconUriId != userId) {
+ Slog.w(TAG, "sanitizeSlice() user: " + userId + " cannot access icons in Slice");
+ return null;
+ }
+ }
+
+ return slice;
+ }
@Nullable
static AutofillId[] toArray(@Nullable ArraySet<AutofillId> set) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
index 38a412fa063d..50a26b355537 100644
--- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
@@ -27,6 +27,7 @@ import android.service.autofill.InlinePresentation;
import android.util.Slog;
import com.android.server.LocalServices;
+import com.android.server.autofill.Helper;
import com.android.server.autofill.RemoteInlineSuggestionRenderService;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -83,6 +84,10 @@ final class RemoteInlineSuggestionViewConnector {
*/
public boolean renderSuggestion(int width, int height,
@NonNull IInlineSuggestionUiCallback callback) {
+ if (Helper.sanitizeSlice(mInlinePresentation.getSlice()) == null) {
+ if (sDebug) Slog.d(TAG, "Skipped rendering inline suggestion.");
+ return false;
+ }
if (mRemoteRenderService != null) {
if (sDebug) Slog.d(TAG, "Request to recreate the UI");
mRemoteRenderService.renderSuggestion(callback, mInlinePresentation, width, height,
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index d53f949753d8..fcb7934f7ca0 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -60,3 +60,12 @@ flag {
bug: "331749778"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_restricted_mode_changes"
+ namespace: "onboarding"
+ description: "Enables the new framework behavior of not putting apps in restricted mode for "
+ "B&R operations in certain cases."
+ bug: "376661510"
+ is_fixed_read_only: true
+}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 466d477992b3..5de2fb30ac78 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -43,6 +43,7 @@ import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.AppGlobals;
+import android.app.ApplicationThreadConstants;
import android.app.IActivityManager;
import android.app.IBackupAgent;
import android.app.PendingIntent;
@@ -59,6 +60,9 @@ import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
import android.app.backup.ISelectBackupTransportCallback;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -298,6 +302,15 @@ public class UserBackupManagerService {
private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
+ /**
+ * Enables the OS making a decision on whether backup restricted mode should be used for apps
+ * that haven't explicitly opted in or out. See
+ * {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE} for details.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ public static final long OS_DECIDES_BACKUP_RESTRICTED_MODE = 376661510;
+
// Time delay for initialization operations that can be delayed so as not to consume too much
// CPU on bring-up and increase time-to-UI.
private static final long INITIALIZATION_DELAY_MILLIS = 3000;
@@ -352,6 +365,9 @@ public class UserBackupManagerService {
// Backups that we haven't started yet. Keys are package names.
private final HashMap<String, BackupRequest> mPendingBackups = new HashMap<>();
+ private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>();
+ private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>();
+
// locking around the pending-backup management
private final Object mQueueLock = new Object();
@@ -523,7 +539,8 @@ public class UserBackupManagerService {
@VisibleForTesting
UserBackupManagerService(Context context, PackageManager packageManager,
LifecycleOperationStorage operationStorage, TransportManager transportManager,
- BackupHandler backupHandler, BackupManagerConstants backupManagerConstants) {
+ BackupHandler backupHandler, BackupManagerConstants backupManagerConstants,
+ IActivityManager activityManager, ActivityManagerInternal activityManagerInternal) {
mContext = context;
mUserId = 0;
@@ -534,6 +551,8 @@ public class UserBackupManagerService {
mFullBackupQueue = new ArrayList<>();
mBackupHandler = backupHandler;
mConstants = backupManagerConstants;
+ mActivityManager = activityManager;
+ mActivityManagerInternal = activityManagerInternal;
mBaseStateDir = null;
mDataDir = null;
@@ -543,13 +562,11 @@ public class UserBackupManagerService {
mRunInitReceiver = null;
mRunInitIntent = null;
mAgentTimeoutParameters = null;
- mActivityManagerInternal = null;
mAlarmManager = null;
mWakelock = null;
mBackupPreferences = null;
mBackupPasswordManager = null;
mPackageManagerBinder = null;
- mActivityManager = null;
mBackupManagerBinder = null;
mScheduledBackupEligibility = null;
}
@@ -1651,9 +1668,11 @@ public class UserBackupManagerService {
synchronized (mAgentConnectLock) {
mConnecting = true;
mConnectedAgent = null;
+ boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode,
+ app.packageName);
try {
if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId,
- backupDestination)) {
+ backupDestination, useRestrictedMode)) {
Slog.d(TAG, addUserIdToLogMessage(mUserId, "awaiting agent for " + app));
// success; wait for the agent to arrive
@@ -3103,6 +3122,91 @@ public class UserBackupManagerService {
}
}
+ /**
+ * Marks the given set of packages as packages that should not be put into restricted mode if
+ * they are started for the given {@link BackupAnnotations.OperationType}.
+ */
+ public void setNoRestrictedModePackages(Set<String> packageNames,
+ @BackupAnnotations.OperationType int opType) {
+ if (opType == BackupAnnotations.OperationType.BACKUP) {
+ mBackupNoRestrictedModePackages.clear();
+ mBackupNoRestrictedModePackages.addAll(packageNames);
+ } else if (opType == BackupAnnotations.OperationType.RESTORE) {
+ mRestoreNoRestrictedModePackages.clear();
+ mRestoreNoRestrictedModePackages.addAll(packageNames);
+ } else {
+ throw new IllegalArgumentException("opType must be BACKUP or RESTORE");
+ }
+ }
+
+ /**
+ * Clears the list of packages that should not be put into restricted mode for either backup or
+ * restore.
+ */
+ public void clearNoRestrictedModePackages() {
+ mBackupNoRestrictedModePackages.clear();
+ mRestoreNoRestrictedModePackages.clear();
+ }
+
+ /**
+ * If the app has specified {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE}, then
+ * its value is returned. If it hasn't and it targets an SDK below
+ * {@link Build.VERSION_CODES#BAKLAVA} then returns true. If it targets a newer SDK, then
+ * returns the decision made by the {@link android.app.backup.BackupTransport}.
+ *
+ * <p>When this method is called, we should have already asked the transport and cached its
+ * response in {@link #mBackupNoRestrictedModePackages} or
+ * {@link #mRestoreNoRestrictedModePackages} so this method will immediately return without
+ * any IPC to the transport.
+ */
+ private boolean shouldUseRestrictedBackupModeForPackage(
+ @BackupAnnotations.OperationType int mode, String packageName) {
+ if (!Flags.enableRestrictedModeChanges()) {
+ return true;
+ }
+
+ // Key/Value apps are never put in restricted mode.
+ if (mode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
+ || mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) {
+ return false;
+ }
+
+ try {
+ PackageManager.Property property = mPackageManager.getPropertyAsUser(
+ PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE,
+ packageName, /* className= */ null,
+ mUserId);
+ if (property.isBoolean()) {
+ // If the package has explicitly specified, we won't ask the transport.
+ return property.getBoolean();
+ } else {
+ Slog.w(TAG, PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE
+ + "must be a boolean.");
+ }
+ } catch (NameNotFoundException e) {
+ // This is expected when the package has not defined the property in its manifest.
+ }
+
+ // The package has not specified the property. The behavior depends on the package's
+ // targetSdk.
+ // <36 gets the old behavior of always using restricted mode.
+ if (!CompatChanges.isChangeEnabled(OS_DECIDES_BACKUP_RESTRICTED_MODE, packageName,
+ UserHandle.of(mUserId))) {
+ return true;
+ }
+
+ // Apps targeting >=36 get the behavior decided by the transport.
+ // By this point, we should have asked the transport and cached its decision.
+ if ((mode == ApplicationThreadConstants.BACKUP_MODE_FULL
+ && mBackupNoRestrictedModePackages.contains(packageName))
+ || (mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL
+ && mRestoreNoRestrictedModePackages.contains(packageName))) {
+ Slog.d(TAG, "Transport requested no restricted mode for: " + packageName);
+ return false;
+ }
+ return true;
+ }
+
private boolean startConfirmationUi(int token, String action) {
try {
Intent confIntent = new Intent(action);
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index cca166b0939c..be9cdc8692cb 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -16,6 +16,8 @@
package com.android.server.backup.fullbackup;
+import static android.app.backup.BackupAnnotations.OperationType.BACKUP;
+
import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING;
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
@@ -34,6 +36,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -388,6 +391,10 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
}
+ // We ask the transport which packages should not be put in restricted mode and cache
+ // the result in UBMS to be used later when the apps are started for backup.
+ setNoRestrictedModePackages(transport, mPackages);
+
// Set up to send data to the transport
final int N = mPackages.size();
int chunkSizeInBytes = 8 * 1024; // 8KB
@@ -694,6 +701,9 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
mUserBackupManagerService.scheduleNextFullBackupJob(backoff);
}
+ // Clear this to avoid using the memory until reboot.
+ mUserBackupManagerService.clearNoRestrictedModePackages();
+
Slog.i(TAG, "Full data backup pass finished.");
mUserBackupManagerService.getWakelock().release();
}
@@ -722,6 +732,21 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
}
+ private void setNoRestrictedModePackages(BackupTransportClient transport,
+ List<PackageInfo> packages) {
+ try {
+ Set<String> packageNames = new ArraySet<>();
+ for (int i = 0; i < packages.size(); i++) {
+ packageNames.add(packages.get(i).packageName);
+ }
+ packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames,
+ BACKUP);
+ mUserBackupManagerService.setNoRestrictedModePackages(packageNames, BACKUP);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Failed to retrieve no restricted mode packages from transport");
+ }
+ }
+
// Run the backup and pipe it back to the given socket -- expects to run on
// a standalone thread. The runner owns this half of the pipe, and closes
// it to indicate EOD to the other end.
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 e536876f6cc3..5ee51a5aa189 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -53,6 +53,7 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.ArraySet;
import android.util.EventLog;
import android.util.Slog;
@@ -482,6 +483,10 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
return;
}
+ // We ask the transport which packages should not be put in restricted mode and cache
+ // the result in UBMS to be used later when the apps are started for restore.
+ setNoRestrictedModePackages(transport, packages);
+
RestoreDescription desc = transport.nextRestorePackage();
if (desc == null) {
Slog.e(TAG, "No restore metadata available; halting");
@@ -1358,6 +1363,9 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// Clear any ongoing session timeout.
backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
+ // Clear this to avoid using the memory until reboot.
+ backupManagerService.clearNoRestrictedModePackages();
+
// If we have a PM token, we must under all circumstances be sure to
// handshake when we've finished.
if (mPmToken > 0) {
@@ -1819,4 +1827,20 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
return packageInfo;
}
+
+ @VisibleForTesting
+ void setNoRestrictedModePackages(BackupTransportClient transport,
+ PackageInfo[] packages) {
+ try {
+ Set<String> packageNames = new ArraySet<>();
+ for (int i = 0; i < packages.length; i++) {
+ packageNames.add(packages[i].packageName);
+ }
+ packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames,
+ RESTORE);
+ backupManagerService.setNoRestrictedModePackages(packageNames, RESTORE);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Failed to retrieve restricted mode packages from transport");
+ }
+ }
}
diff --git a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
index daf3415229ea..373811fef802 100644
--- a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -17,6 +17,7 @@
package com.android.server.backup.transport;
import android.annotation.Nullable;
+import android.app.backup.BackupAnnotations;
import android.app.backup.BackupTransport;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.RestoreDescription;
@@ -26,6 +27,7 @@ import android.content.pm.PackageInfo;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.backup.IBackupTransport;
@@ -375,6 +377,26 @@ public class BackupTransportClient {
}
/**
+ * See
+ * {@link IBackupTransport#getPackagesThatShouldNotUseRestrictedMode(List, int, AndroidFuture)}.
+ */
+ public Set<String> getPackagesThatShouldNotUseRestrictedMode(Set<String> packageNames,
+ @BackupAnnotations.OperationType
+ int operationType) throws RemoteException {
+ AndroidFuture<List<String>> resultFuture = mTransportFutures.newFuture();
+ mTransportBinder.getPackagesThatShouldNotUseRestrictedMode(List.copyOf(packageNames),
+ operationType,
+ resultFuture);
+ List<String> resultList = getFutureResult(resultFuture);
+ Set<String> set = new ArraySet<>();
+ if (resultList == null) {
+ return set;
+ }
+ set.addAll(resultList);
+ return set;
+ }
+
+ /**
* Allows the {@link TransportConnection} to notify this client
* if the underlying transport has become unusable. If that happens
* we want to cancel all active futures or callbacks.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index dfddc089e4a4..d880bce921aa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -478,7 +478,6 @@ import com.android.server.wm.WindowProcessController;
import dalvik.annotation.optimization.NeverCompile;
import dalvik.system.VMRuntime;
-
import libcore.util.EmptyArray;
import java.io.File;
@@ -4493,16 +4492,11 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.w(TAG, "Unattached app died before backup, skipping");
final int userId = app.userId;
final String packageName = app.info.packageName;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- IBackupManager bm = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- bm.agentDisconnectedForUser(userId, packageName);
- } catch (RemoteException e) {
- // Can't happen; the backup manager is local
- }
+ mHandler.post(() -> {
+ try {
+ getBackupManager().agentDisconnectedForUser(userId, packageName);
+ } catch (RemoteException e) {
+ // Can't happen; the backup manager is local
}
});
}
@@ -4673,7 +4667,8 @@ public class ActivityManagerService extends IActivityManager.Stub
if (backupTarget != null && backupTarget.appInfo.packageName.equals(processName)) {
isRestrictedBackupMode = backupTarget.appInfo.uid >= FIRST_APPLICATION_UID
&& ((backupTarget.backupMode == BackupRecord.RESTORE_FULL)
- || (backupTarget.backupMode == BackupRecord.BACKUP_FULL));
+ || (backupTarget.backupMode == BackupRecord.BACKUP_FULL))
+ && backupTarget.useRestrictedMode;
}
final ActiveInstrumentation instr = app.getActiveInstrumentation();
@@ -13499,16 +13494,11 @@ public class ActivityManagerService extends IActivityManager.Stub
if (backupTarget != null && pid == backupTarget.app.getPid()) {
if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG_CLEANUP, "App "
+ backupTarget.appInfo + " died during backup");
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- IBackupManager bm = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- bm.agentDisconnectedForUser(app.userId, app.info.packageName);
- } catch (RemoteException e) {
- // can't happen; backup manager is local
- }
+ mHandler.post(() -> {
+ try {
+ getBackupManager().agentDisconnectedForUser(app.userId, app.info.packageName);
+ } catch (RemoteException e) {
+ // can't happen; backup manager is local
}
});
}
@@ -14011,7 +14001,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// instantiated. The backup agent will invoke backupAgentCreated() on the
// activity manager to announce its creation.
public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId,
- @BackupDestination int backupDestination) {
+ @BackupDestination int backupDestination, boolean useRestrictedMode) {
long startTimeNs = SystemClock.uptimeNanos();
if (DEBUG_BACKUP) {
Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode
@@ -14096,7 +14086,8 @@ public class ActivityManagerService extends IActivityManager.Stub
+ app.packageName + ": " + e);
}
- BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination);
+ BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination,
+ useRestrictedMode);
ComponentName hostingName =
(backupMode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
|| backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE)
@@ -14122,8 +14113,9 @@ public class ActivityManagerService extends IActivityManager.Stub
// process, etc, then mark it as being in full backup so that certain calls to the
// process can be blocked. This is not reset to false anywhere because we kill the
// process after the full backup is done and the ProcessRecord will vaporize anyway.
- if (UserHandle.isApp(app.uid) &&
- backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL) {
+ if (UserHandle.isApp(app.uid)
+ && backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL
+ && r.useRestrictedMode) {
proc.setInFullBackup(true);
}
r.app = proc;
@@ -14221,9 +14213,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final long oldIdent = Binder.clearCallingIdentity();
try {
- IBackupManager bm = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- bm.agentConnectedForUser(userId, agentPackageName, agent);
+ getBackupManager().agentConnectedForUser(userId, agentPackageName, agent);
} catch (RemoteException e) {
// can't happen; the backup manager service is local
} catch (Exception e) {
@@ -18013,14 +18003,6 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void addStartInfoTimestamp(int key, long timestampNs, int uid, int pid,
int userId) {
- // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
- if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
- throw new IllegalArgumentException("Unsupported userId");
- }
-
- mUserController.handleIncomingUser(pid, uid, userId, true,
- ALLOW_NON_FULL, "addStartInfoTimestampSystem", null);
-
addStartInfoTimestampInternal(key, timestampNs, userId, uid);
}
@@ -19353,4 +19335,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
return token;
}
+
+ private IBackupManager getBackupManager() {
+ return IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE));
+ }
}
diff --git a/services/core/java/com/android/server/am/BackupRecord.java b/services/core/java/com/android/server/am/BackupRecord.java
index 0b056d7883bf..64cc6f0e66e3 100644
--- a/services/core/java/com/android/server/am/BackupRecord.java
+++ b/services/core/java/com/android/server/am/BackupRecord.java
@@ -32,15 +32,18 @@ final class BackupRecord {
final int userId; // user for which backup is performed
final int backupMode; // full backup / incremental / restore
@BackupDestination final int backupDestination; // see BackupAnnotations#BackupDestination
+ final boolean useRestrictedMode; // whether the app should be put into restricted backup mode
ProcessRecord app; // where this agent is running or null
// ----- Implementation -----
- BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination) {
+ BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination,
+ boolean _useRestrictedMode) {
appInfo = _appInfo;
backupMode = _backupMode;
userId = _userId;
backupDestination = _backupDestination;
+ useRestrictedMode = _useRestrictedMode;
}
public String toString() {
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index 446b3671a94a..c6cb67f4efa8 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -553,7 +553,7 @@ class BroadcastController {
}
BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps,
- exported, mService.mPlatformCompat);
+ exported, callerApp.info, mService.mPlatformCompat);
if (rl.containsFilter(filter)) {
Slog.w(TAG, "Receiver with filter " + filter
+ " already registered for pid " + rl.pid
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index 3c7fb52b11b4..a32d3cb86c24 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -16,10 +16,13 @@
package com.android.server.am;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.os.Binder;
import android.os.UserHandle;
import android.util.PrintWriterPrinter;
import android.util.Printer;
@@ -40,7 +43,7 @@ public final class BroadcastFilter extends IntentFilter {
@ChangeId
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE)
@VisibleForTesting
- static final long CHANGE_RESTRICT_PRIORITY_VALUES = 371309185L;
+ static final long RESTRICT_PRIORITY_VALUES = 371309185L;
// Back-pointer to the list this filter is in.
final ReceiverList receiverList;
@@ -54,11 +57,12 @@ public final class BroadcastFilter extends IntentFilter {
final boolean visibleToInstantApp;
public final boolean exported;
final int initialPriority;
+ final ApplicationInfo applicationInfo;
BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList,
String _packageName, String _featureId, String _receiverId, String _requiredPermission,
int _owningUid, int _userId, boolean _instantApp, boolean _visibleToInstantApp,
- boolean _exported, PlatformCompat platformCompat) {
+ boolean _exported, ApplicationInfo _applicationInfo, PlatformCompat platformCompat) {
super(_filter);
receiverList = _receiverList;
packageName = _packageName;
@@ -70,8 +74,10 @@ public final class BroadcastFilter extends IntentFilter {
instantApp = _instantApp;
visibleToInstantApp = _visibleToInstantApp;
exported = _exported;
+ applicationInfo = _applicationInfo;
initialPriority = getPriority();
- setPriority(calculateAdjustedPriority(owningUid, initialPriority, platformCompat));
+ setPriority(calculateAdjustedPriority(owningUid, initialPriority,
+ applicationInfo, platformCompat));
}
public @Nullable String getReceiverClassName() {
@@ -84,6 +90,10 @@ public final class BroadcastFilter extends IntentFilter {
return null;
}
+ public @NonNull ApplicationInfo getApplicationInfo() {
+ return applicationInfo;
+ }
+
@NeverCompile
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
@@ -125,13 +135,18 @@ public final class BroadcastFilter extends IntentFilter {
@VisibleForTesting
static int calculateAdjustedPriority(int owningUid, int priority,
- PlatformCompat platformCompat) {
+ ApplicationInfo applicationInfo, PlatformCompat platformCompat) {
if (!Flags.restrictPriorityValues()) {
return priority;
}
- if (!platformCompat.isChangeEnabledByUidInternalNoLogging(
- CHANGE_RESTRICT_PRIORITY_VALUES, owningUid)) {
- return priority;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!platformCompat.isChangeEnabledInternalNoLogging(
+ RESTRICT_PRIORITY_VALUES, applicationInfo)) {
+ return priority;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
if (!UserHandle.isCore(owningUid)) {
if (priority >= SYSTEM_HIGH_PRIORITY) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 38df10a0bc8c..a1ab1eea3d3e 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -49,6 +49,7 @@ import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Bundle;
@@ -57,7 +58,6 @@ import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.IntArray;
import android.util.PrintWriterPrinter;
-import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -88,7 +88,7 @@ final class BroadcastRecord extends Binder {
@ChangeId
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE)
@VisibleForTesting
- static final long CHANGE_LIMIT_PRIORITY_SCOPE = 371307720L;
+ static final long LIMIT_PRIORITY_SCOPE = 371307720L;
final @NonNull Intent intent; // the original intent that generated us
final @Nullable ComponentName targetComp; // original component name set on the intent
@@ -781,7 +781,7 @@ final class BroadcastRecord extends Binder {
} else {
if (Flags.limitPriorityScope()) {
final boolean[] changeEnabled = calculateChangeStateForReceivers(
- receivers, CHANGE_LIMIT_PRIORITY_SCOPE, platformCompat);
+ receivers, LIMIT_PRIORITY_SCOPE, platformCompat);
// Priority of the previous tranche
int lastTranchePriority = 0;
@@ -865,25 +865,35 @@ final class BroadcastRecord extends Binder {
@VisibleForTesting
static @NonNull boolean[] calculateChangeStateForReceivers(@NonNull List<Object> receivers,
long changeId, PlatformCompat platformCompat) {
- final SparseBooleanArray changeStateForUids = new SparseBooleanArray();
+ // TODO: b/371307720 - Remove this method as we are already avoiding the packagemanager
+ // calls by checking the changeId state using ApplicationInfos.
+ final ArrayMap<String, Boolean> changeStates = new ArrayMap<>();
final int count = receivers.size();
final boolean[] changeStateForReceivers = new boolean[count];
for (int i = 0; i < count; ++i) {
- final int receiverUid = getReceiverUid(receivers.get(i));
+ final ApplicationInfo receiverAppInfo = getReceiverAppInfo(receivers.get(i));
final boolean isChangeEnabled;
- final int idx = changeStateForUids.indexOfKey(receiverUid);
+ final int idx = changeStates.indexOfKey(receiverAppInfo.packageName);
if (idx >= 0) {
- isChangeEnabled = changeStateForUids.valueAt(idx);
+ isChangeEnabled = changeStates.valueAt(idx);
} else {
- isChangeEnabled = platformCompat.isChangeEnabledByUidInternalNoLogging(
- changeId, receiverUid);
- changeStateForUids.put(receiverUid, isChangeEnabled);
+ isChangeEnabled = platformCompat.isChangeEnabledInternalNoLogging(
+ changeId, receiverAppInfo);
+ changeStates.put(receiverAppInfo.packageName, isChangeEnabled);
}
changeStateForReceivers[i] = isChangeEnabled;
}
return changeStateForReceivers;
}
+ static ApplicationInfo getReceiverAppInfo(@NonNull Object receiver) {
+ if (receiver instanceof BroadcastFilter) {
+ return ((BroadcastFilter) receiver).getApplicationInfo();
+ } else {
+ return ((ResolveInfo) receiver).activityInfo.applicationInfo;
+ }
+ }
+
static int getReceiverUid(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).owningUid;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b51db137f293..98f738c38d63 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -351,7 +351,8 @@ class ProcessRecord implements WindowProcessListener {
private String[] mIsolatedEntryPointArgs;
/**
- * Process is currently hosting a backup agent for backup or restore.
+ * Process is currently hosting a backup agent for backup or restore. Note that this is only set
+ * when the process is put into restricted backup mode.
*/
@GuardedBy("mService")
private boolean mInFullBackup;
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 364497491785..14d3fbc22372 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -393,6 +393,8 @@ final class ProcessServiceRecord {
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
} else if (adj < ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
adj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
+ } else if (Flags.addModifyRawOomAdjServiceLevel() && adj < ProcessList.SERVICE_ADJ) {
+ adj = ProcessList.SERVICE_ADJ;
} else if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
adj = ProcessList.CACHED_APP_MIN_ADJ;
} else if (adj < ProcessList.CACHED_APP_MAX_ADJ) {
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 711b163ea424..c59c40fc9cd8 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -260,3 +260,13 @@ flag {
description: "Use PROCESS_CAPABILITY_CPU_TIME to control unfreeze state."
bug: "370817323"
}
+
+flag {
+ name: "add_modify_raw_oom_adj_service_level"
+ namespace: "backstage_power"
+ description: "Add a SERVICE_ADJ level to the modifyRawOomAdj method"
+ bug: "374810368"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6ba356990cac..5f716605e8cd 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9224,6 +9224,9 @@ public class AudioService extends IAudioService.Stub
return;
}
+ // index values sent to APM are in the stream type SDK range, not *10
+ int indexMinVolCurve = MIN_STREAM_VOLUME[mStreamType];
+ int indexMaxVolCurve = MAX_STREAM_VOLUME[mStreamType];
synchronized (this) {
if (mStreamType == AudioSystem.STREAM_VOICE_CALL) {
if (MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO]
@@ -9234,11 +9237,15 @@ public class AudioService extends IAudioService.Stub
if (!equalScoLeaVcIndexRange() && isStreamBluetoothSco(mStreamType)) {
// SCO devices have a different min index
mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
+ indexMinVolCurve = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
+ indexMaxVolCurve = MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
mIndexStepFactor = 1.f;
} else if (equalScoLeaVcIndexRange() && isStreamBluetoothComm(mStreamType)) {
// For non SCO devices the stream state does not change the min index
if (mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
+ indexMinVolCurve = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
+ indexMaxVolCurve = MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
} else {
mIndexMin = MIN_STREAM_VOLUME[mStreamType] * 10;
}
@@ -9259,7 +9266,7 @@ public class AudioService extends IAudioService.Stub
}
final int status = AudioSystem.initStreamVolume(
- mStreamType, mIndexMin / 10, mIndexMax / 10);
+ mStreamType, indexMinVolCurve, indexMaxVolCurve);
sVolumeLogger.enqueue(new EventLogger.StringEvent(
"updateIndexFactors() stream:" + mStreamType + " index min/max:"
+ mIndexMin / 10 + "/" + mIndexMax / 10 + " indexStepFactor:"
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index b63b07f0453e..e92b5188652b 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -20,6 +20,7 @@ import static android.media.AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_MUTE;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_MASTER;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_PORT_VOLUME;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_MUTED;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_VOLUME;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_VOLUME_SHAPER;
@@ -444,7 +445,7 @@ public final class PlaybackActivityMonitor
}
if (DEBUG) {
- Log.v(TAG, TextUtils.formatSimple("BLA portEvent(portId=%d, event=%s, extras=%s)",
+ Log.v(TAG, TextUtils.formatSimple("portEvent(portId=%d, event=%s, extras=%s)",
portId, AudioPlaybackConfiguration.playerStateToString(event), extras));
}
@@ -1381,6 +1382,9 @@ public final class PlaybackActivityMonitor
if ((mEventValue & MUTED_BY_VOLUME_SHAPER) != 0) {
builder.append("volumeShaper ");
}
+ if ((mEventValue & MUTED_BY_PORT_VOLUME) != 0) {
+ builder.append("portVolume ");
+ }
}
return builder.toString();
default:
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index afdc0c0294a6..6ed1ac859501 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -226,10 +226,6 @@ class PreAuthInfo {
return BIOMETRIC_NO_HARDWARE;
}
- if (sensor.modality == TYPE_FACE && biometricCameraManager.isAnyCameraUnavailable()) {
- return BIOMETRIC_HARDWARE_NOT_DETECTED;
- }
-
final boolean wasStrongEnough =
Utils.isAtLeastStrength(sensor.oemStrength, requestedStrength);
final boolean isStrongEnough =
@@ -241,6 +237,10 @@ class PreAuthInfo {
return BIOMETRIC_INSUFFICIENT_STRENGTH;
}
+ if (sensor.modality == TYPE_FACE && biometricCameraManager.isAnyCameraUnavailable()) {
+ return BIOMETRIC_HARDWARE_NOT_DETECTED;
+ }
+
try {
if (!sensor.impl.isHardwareDetected(opPackageName)) {
return BIOMETRIC_HARDWARE_NOT_DETECTED;
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index eeac26031719..6feae34f1a2d 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -522,7 +522,7 @@ public class PlatformCompat extends IPlatformCompat.Stub {
// b/282922910 - we don't want apps sharing system uid and targeting
// older target sdk to impact all system uid apps
if (Flags.systemUidTargetSystemSdk() && !mIsWear &&
- uid == Process.SYSTEM_UID) {
+ uid == Process.SYSTEM_UID && appInfo != null) {
appInfo.targetSdkVersion = Build.VERSION.SDK_INT;
}
return appInfo;
diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java
index 716661dd6c3c..68dc80fa51c8 100644
--- a/services/core/java/com/android/server/display/color/TintController.java
+++ b/services/core/java/com/android/server/display/color/TintController.java
@@ -19,6 +19,7 @@ package com.android.server.display.color;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
@@ -29,23 +30,33 @@ abstract class TintController {
*/
private static final long TRANSITION_DURATION = 3000L;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
private ValueAnimator mAnimator;
+ @GuardedBy("mLock")
private Boolean mIsActivated;
public ValueAnimator getAnimator() {
- return mAnimator;
+ synchronized (mLock) {
+ return mAnimator;
+ }
}
public void setAnimator(ValueAnimator animator) {
- mAnimator = animator;
+ synchronized (mLock) {
+ mAnimator = animator;
+ }
}
/**
* Cancel the animator if it's still running.
*/
public void cancelAnimator() {
- if (mAnimator != null) {
- mAnimator.cancel();
+ synchronized (mLock) {
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
}
}
@@ -53,22 +64,30 @@ abstract class TintController {
* End the animator if it's still running, jumping to the end state.
*/
public void endAnimator() {
- if (mAnimator != null) {
- mAnimator.end();
- mAnimator = null;
+ synchronized (mLock) {
+ if (mAnimator != null) {
+ mAnimator.end();
+ mAnimator = null;
+ }
}
}
public void setActivated(Boolean isActivated) {
- mIsActivated = isActivated;
+ synchronized (mLock) {
+ mIsActivated = isActivated;
+ }
}
public boolean isActivated() {
- return mIsActivated != null && mIsActivated;
+ synchronized (mLock) {
+ return mIsActivated != null && mIsActivated;
+ }
}
public boolean isActivatedStateNotSet() {
- return mIsActivated == null;
+ synchronized (mLock) {
+ return mIsActivated == null;
+ }
}
public long getTransitionDurationMilliseconds() {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 586d59492f57..e7ea868ca04f 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -4,6 +4,14 @@ container: "system"
# Important: Flags must be accessed through DisplayManagerFlags.
flag {
+ name: "is_always_on_available_api"
+ namespace: "display_manager"
+ description: "Allows querying of AOD availability"
+ bug: "324046664"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_port_in_display_layout"
namespace: "display_manager"
description: "Allows refering to displays by port in display layout"
diff --git a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
index aef207f9c027..f3820e5935d4 100644
--- a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
+++ b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
@@ -21,6 +21,7 @@ import android.annotation.SuppressLint;
import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
+import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.hardware.input.AppLaunchData;
import android.hardware.input.InputGestureData;
@@ -137,11 +138,19 @@ final class AppLaunchShortcutManager {
String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
String roleName = parser.getAttributeValue(null, ATTRIBUTE_ROLE);
-
- // TODO(b/358569822): Shift bookmarks to use keycode instead of shortcutChar
- int keycode = KeyEvent.KEYCODE_UNKNOWN;
String shortcut = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
- if (!TextUtils.isEmpty(shortcut)) {
+ int keycode;
+ int modifierState;
+ TypedArray a = mContext.getResources().obtainAttributes(parser,
+ R.styleable.Bookmark);
+ try {
+ keycode = a.getInt(R.styleable.Bookmark_keycode, KeyEvent.KEYCODE_UNKNOWN);
+ modifierState = a.getInt(R.styleable.Bookmark_modifierState, 0);
+ } finally {
+ a.recycle();
+ }
+ if (keycode == KeyEvent.KEYCODE_UNKNOWN && !TextUtils.isEmpty(shortcut)) {
+ // Fetch keycode using shortcut char
KeyEvent[] events = virtualKcm.getEvents(new char[]{shortcut.toLowerCase(
Locale.ROOT).charAt(0)});
// Single key press can generate the character
@@ -153,12 +162,17 @@ final class AppLaunchShortcutManager {
Log.w(TAG, "Keycode required for bookmark with category=" + categoryName
+ " packageName=" + packageName + " className=" + className
+ " role=" + roleName + " shiftName=" + shiftName
- + " shortcut=" + shortcut);
+ + " shortcut=" + shortcut + " modifierState=" + modifierState);
continue;
}
- final boolean isShiftShortcut = (shiftName != null && shiftName.toLowerCase(
- Locale.ROOT).equals("true"));
+ if (modifierState == 0) {
+ // Fetch modifierState using shiftName
+ boolean isShiftShortcut = shiftName != null && shiftName.toLowerCase(
+ Locale.ROOT).equals("true");
+ modifierState =
+ KeyEvent.META_META_ON | (isShiftShortcut ? KeyEvent.META_SHIFT_ON : 0);
+ }
AppLaunchData launchData = null;
if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
launchData = AppLaunchData.createLaunchDataForComponent(packageName, className);
@@ -168,11 +182,9 @@ final class AppLaunchShortcutManager {
launchData = AppLaunchData.createLaunchDataForRole(roleName);
}
if (launchData != null) {
- Log.d(TAG, "adding shortcut " + launchData + "shift="
- + isShiftShortcut + " keycode=" + keycode);
+ Log.d(TAG, "adding shortcut " + launchData + " modifierState="
+ + modifierState + " keycode=" + keycode);
// All bookmarks are based on Action key
- int modifierState =
- KeyEvent.META_META_ON | (isShiftShortcut ? KeyEvent.META_SHIFT_ON : 0);
InputGestureData bookmark = new InputGestureData.Builder()
.setTrigger(InputGestureData.createKeyTrigger(keycode, modifierState))
.setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 82449ce89c2c..edad2473061c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -193,7 +193,6 @@ public class InputManagerService extends IInputManager.Stub
private DisplayManagerInternal mDisplayManagerInternal;
private WindowManagerInternal mWindowManagerInternal;
- private PackageManagerInternal mPackageManagerInternal;
private final File mDoubleTouchGestureEnableFile;
@@ -573,7 +572,6 @@ public class InputManagerService extends IInputManager.Stub
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
- mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mSettingsObserver.registerAndUpdate();
@@ -2937,10 +2935,11 @@ public class InputManagerService extends IInputManager.Stub
private void enforceManageKeyGesturePermission() {
// TODO(b/361567988): Use @EnforcePermission to enforce permission once flag guarding the
// permission is rolled out
- if (mSystemReady) {
- String systemUIPackage = mContext.getString(R.string.config_systemUi);
- int systemUIAppId = UserHandle.getAppId(mPackageManagerInternal
- .getPackageUid(systemUIPackage, PackageManager.MATCH_SYSTEM_ONLY,
+ String systemUIPackage = mContext.getString(R.string.config_systemUi);
+ PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
+ if (pm != null) {
+ int systemUIAppId = UserHandle.getAppId(
+ pm.getPackageUid(systemUIPackage, PackageManager.MATCH_SYSTEM_ONLY,
UserHandle.USER_SYSTEM));
if (UserHandle.getCallingAppId() == systemUIAppId) {
return;
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
new file mode 100644
index 000000000000..c05f7a0c0e00
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.location.contexthub;
+
+import android.hardware.contexthub.EndpointId;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IEndpointCallback;
+import android.hardware.contexthub.Message;
+import android.hardware.contexthub.MessageDeliveryStatus;
+import android.os.RemoteException;
+
+/** IEndpointCallback implementation. */
+public class ContextHubHalEndpointCallback
+ extends android.hardware.contexthub.IEndpointCallback.Stub {
+ private final IEndpointLifecycleCallback mEndpointLifecycleCallback;
+
+ /** Interface for listening for endpoint start and stop events. */
+ public interface IEndpointLifecycleCallback {
+ /** Called when a batch of endpoints started. */
+ void onEndpointStarted(HubEndpointInfo[] endpointInfos);
+
+ /** Called when a batch of endpoints stopped. */
+ void onEndpointStopped(HubEndpointInfo.HubEndpointIdentifier[] endpointIds, byte reason);
+ }
+
+ ContextHubHalEndpointCallback(IEndpointLifecycleCallback endpointLifecycleCallback) {
+ mEndpointLifecycleCallback = endpointLifecycleCallback;
+ }
+
+ @Override
+ public void onEndpointStarted(android.hardware.contexthub.EndpointInfo[] halEndpointInfos)
+ throws RemoteException {
+ if (halEndpointInfos.length == 0) {
+ return;
+ }
+ HubEndpointInfo[] endpointInfos = new HubEndpointInfo[halEndpointInfos.length];
+ for (int i = 0; i < halEndpointInfos.length; i++) {
+ endpointInfos[i++] = new HubEndpointInfo(halEndpointInfos[i]);
+ }
+ mEndpointLifecycleCallback.onEndpointStarted(endpointInfos);
+ }
+
+ @Override
+ public void onEndpointStopped(EndpointId[] halEndpointIds, byte reason) throws RemoteException {
+ HubEndpointInfo.HubEndpointIdentifier[] endpointIds =
+ new HubEndpointInfo.HubEndpointIdentifier[halEndpointIds.length];
+ for (int i = 0; i < halEndpointIds.length; i++) {
+ endpointIds[i] = new HubEndpointInfo.HubEndpointIdentifier(halEndpointIds[i]);
+ }
+ mEndpointLifecycleCallback.onEndpointStopped(endpointIds, reason);
+ }
+
+ @Override
+ public void onMessageReceived(int i, Message message) throws RemoteException {}
+
+ @Override
+ public void onMessageDeliveryStatusReceived(int i, MessageDeliveryStatus messageDeliveryStatus)
+ throws RemoteException {}
+
+ @Override
+ public void onEndpointSessionOpenRequest(
+ int i, EndpointId endpointId, EndpointId endpointId1, String s)
+ throws RemoteException {}
+
+ @Override
+ public void onCloseEndpointSession(int i, byte b) throws RemoteException {}
+
+ @Override
+ public void onEndpointSessionOpenComplete(int i) throws RemoteException {}
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ return IEndpointCallback.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return IEndpointCallback.HASH;
+ }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index f611c57dab03..8cf0578523ad 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -31,6 +31,9 @@ import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManagerInternal;
import android.hardware.contexthub.ErrorCode;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IContextHubEndpoint;
+import android.hardware.contexthub.IContextHubEndpointCallback;
import android.hardware.contexthub.MessageDeliveryStatus;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubMessage;
@@ -249,6 +252,7 @@ public class ContextHubService extends IContextHubService.Stub {
public void handleServiceRestart() {
Log.i(TAG, "Recovering from Context Hub HAL restart...");
initExistingCallbacks();
+ mHubInfoRegistry.onHalRestart();
resetSettings();
if (Flags.reconnectHostEndpointsAfterHalRestart()) {
mClientManager.forEachClientOfHub(mContextHubId,
@@ -330,6 +334,7 @@ public class ContextHubService extends IContextHubService.Stub {
}
initDefaultClientMap();
+ initEndpointCallback();
initLocationSettingNotifications();
initWifiSettingNotifications();
@@ -508,6 +513,18 @@ public class ContextHubService extends IContextHubService.Stub {
mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
}
+ private void initEndpointCallback() {
+ if (mHubInfoRegistry == null) {
+ return;
+ }
+ try {
+ mContextHubWrapper.registerEndpointCallback(
+ new ContextHubHalEndpointCallback(mHubInfoRegistry));
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while registering IEndpointCallback", e);
+ }
+ }
+
/**
* Initializes existing callbacks with the mContextHubWrapper for every context hub
*/
@@ -739,6 +756,26 @@ public class ContextHubService extends IContextHubService.Stub {
return mHubInfoRegistry.getHubs();
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public List<HubEndpointInfo> findEndpoints(long endpointId) {
+ super.findEndpoints_enforcePermission();
+ if (mHubInfoRegistry == null) {
+ return Collections.emptyList();
+ }
+ return mHubInfoRegistry.findEndpoints(endpointId);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public IContextHubEndpoint registerEndpoint(
+ HubEndpointInfo pendingHubEndpointInfo, IContextHubEndpointCallback callback)
+ throws RemoteException {
+ super.registerEndpoint_enforcePermission();
+ // TODO(b/375487784): Implement this
+ return null;
+ }
+
/**
* Creates an internal load transaction callback to be used for old API clients
*
diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
index 68de9dbda2e1..4d1000f3e0e5 100644
--- a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
+++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
@@ -16,45 +16,144 @@
package com.android.server.location.contexthub;
+import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.location.HubInfo;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-class HubInfoRegistry {
+class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycleCallback {
private static final String TAG = "HubInfoRegistry";
+ private final Object mLock = new Object();
private final IContextHubWrapper mContextHubWrapper;
- private final List<HubInfo> mHubsInfo;
+ @GuardedBy("mLock")
+ private List<HubInfo> mHubsInfo;
+
+ @GuardedBy("mLock")
+ private final ArrayMap<HubEndpointInfo.HubEndpointIdentifier, HubEndpointInfo>
+ mHubEndpointInfos = new ArrayMap<>();
HubInfoRegistry(IContextHubWrapper contextHubWrapper) {
- List<HubInfo> hubInfos;
mContextHubWrapper = contextHubWrapper;
+ refreshCachedHubs();
+ refreshCachedEndpoints();
+ }
+
+ /** Retrieve the list of hubs available. */
+ List<HubInfo> getHubs() {
+ synchronized (mLock) {
+ return mHubsInfo;
+ }
+ }
+
+ private void refreshCachedHubs() {
+ List<HubInfo> hubInfos;
try {
hubInfos = mContextHubWrapper.getHubs();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while getting Hub info", e);
hubInfos = Collections.emptyList();
}
- mHubsInfo = hubInfos;
+
+ synchronized (mLock) {
+ mHubsInfo = hubInfos;
+ }
}
- /** Retrieve the list of hubs available. */
- List<HubInfo> getHubs() {
- return mHubsInfo;
+ private void refreshCachedEndpoints() {
+ List<HubEndpointInfo> endpointInfos;
+ try {
+ endpointInfos = mContextHubWrapper.getEndpoints();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while getting Hub info", e);
+ endpointInfos = Collections.emptyList();
+ }
+
+ synchronized (mLock) {
+ mHubEndpointInfos.clear();
+ for (HubEndpointInfo endpointInfo : endpointInfos) {
+ mHubEndpointInfos.put(endpointInfo.getIdentifier(), endpointInfo);
+ }
+ }
+ }
+
+ /** Invoked when HAL restarts */
+ public void onHalRestart() {
+ synchronized (mLock) {
+ refreshCachedHubs();
+ refreshCachedEndpoints();
+ }
+ }
+
+ @Override
+ public void onEndpointStarted(HubEndpointInfo[] endpointInfos) {
+ synchronized (mLock) {
+ for (HubEndpointInfo endpointInfo : endpointInfos) {
+ mHubEndpointInfos.remove(endpointInfo.getIdentifier());
+ mHubEndpointInfos.put(endpointInfo.getIdentifier(), endpointInfo);
+ }
+ }
+ }
+
+ @Override
+ public void onEndpointStopped(
+ HubEndpointInfo.HubEndpointIdentifier[] endpointIds, byte reason) {
+ synchronized (mLock) {
+ for (HubEndpointInfo.HubEndpointIdentifier endpointId : endpointIds) {
+ mHubEndpointInfos.remove(endpointId);
+ }
+ }
+ }
+
+ /** Return a list of {@link HubEndpointInfo} that represents endpoints with the matching id. */
+ public List<HubEndpointInfo> findEndpoints(long endpointIdQuery) {
+ List<HubEndpointInfo> searchResult = new ArrayList<>();
+ synchronized (mLock) {
+ for (HubEndpointInfo.HubEndpointIdentifier endpointId : mHubEndpointInfos.keySet()) {
+ if (endpointId.getEndpoint() == endpointIdQuery) {
+ searchResult.add(mHubEndpointInfos.get(endpointId));
+ }
+ }
+ }
+ return searchResult;
}
void dump(IndentingPrintWriter ipw) {
+ synchronized (mLock) {
+ dumpLocked(ipw);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void dumpLocked(IndentingPrintWriter ipw) {
ipw.println(TAG);
ipw.increaseIndent();
+ ipw.println("Hubs");
for (HubInfo hubInfo : mHubsInfo) {
ipw.println(hubInfo);
}
ipw.decreaseIndent();
+
+ ipw.println();
+
+ ipw.increaseIndent();
+ ipw.println("Endpoints");
+ for (HubEndpointInfo endpointInfo : mHubEndpointInfos.values()) {
+ ipw.println(endpointInfo);
+ }
+ ipw.decreaseIndent();
+
+ ipw.println();
}
+
}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 6656a6fe9eb4..9b729eb11eed 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -19,6 +19,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.chre.flags.Flags;
import android.hardware.contexthub.HostEndpointInfo;
+import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.MessageDeliveryStatus;
import android.hardware.contexthub.NanSessionRequest;
import android.hardware.contexthub.V1_0.ContextHub;
@@ -229,6 +230,15 @@ public abstract class IContextHubWrapper {
return Collections.emptyList();
}
+ /** Calls the appropriate getEndpoints function depending on the HAL version. */
+ public List<HubEndpointInfo> getEndpoints() throws RemoteException {
+ return Collections.emptyList();
+ }
+
+ /** Calls the appropriate registerEndpointCallback function depending on the HAL version. */
+ public void registerEndpointCallback(android.hardware.contexthub.IEndpointCallback cb)
+ throws RemoteException {}
+
/**
* @return True if this version of the Contexthub HAL supports Location setting notifications.
*/
@@ -622,6 +632,45 @@ public abstract class IContextHubWrapper {
return retVal;
}
+ @Override
+ public List<HubEndpointInfo> getEndpoints() throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return Collections.emptyList();
+ }
+
+ List<HubEndpointInfo> retVal = new ArrayList<>();
+ final List<android.hardware.contexthub.EndpointInfo> halEndpointInfos =
+ hub.getEndpoints();
+ for (android.hardware.contexthub.EndpointInfo halEndpointInfo : halEndpointInfos) {
+ /* HAL -> API Type conversion */
+ final HubEndpointInfo endpointInfo = new HubEndpointInfo(halEndpointInfo);
+ if (DEBUG) {
+ Log.i(TAG, "getEndpoints: endpointInfo=" + endpointInfo);
+ }
+ retVal.add(endpointInfo);
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "getEndpoints: total count=" + retVal.size());
+ }
+ return retVal;
+ }
+
+ @Override
+ public void registerEndpointCallback(android.hardware.contexthub.IEndpointCallback cb)
+ throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "registerEndpointCallback: cb=" + cb);
+ }
+ hub.registerEndpointCallback(cb);
+ }
+
public boolean supportsLocationSettingNotifications() {
return true;
}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 436acba6e492..c460465bfb11 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -17,7 +17,6 @@
package com.android.server.media.projection;
import static android.Manifest.permission.MANAGE_MEDIA_PROJECTION;
-import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -28,7 +27,6 @@ import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_TASK;
import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN;
-import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
@@ -41,10 +39,7 @@ import android.app.ActivityManagerInternal;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AppOpsManager;
import android.app.IProcessObserver;
-import android.app.KeyguardManager;
import android.app.compat.CompatChanges;
-import android.app.role.RoleManager;
-import android.companion.AssociationRequest;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
@@ -74,7 +69,6 @@ import android.os.PermissionEnforcer;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.ContentRecordingSession;
@@ -85,7 +79,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
-import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.wm.WindowManagerInternal;
@@ -140,12 +133,12 @@ public final class MediaProjectionManagerService extends SystemService
private final ActivityManagerInternal mActivityManagerInternal;
private final PackageManager mPackageManager;
private final WindowManagerInternal mWmInternal;
- private final KeyguardManager mKeyguardManager;
- private final RoleManager mRoleManager;
+
private final MediaRouter mMediaRouter;
private final MediaRouterCallback mMediaRouterCallback;
private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ private final MediaProjectionStopController mMediaProjectionStopController;
private MediaRouter.RouteInfo mMediaRouteInfo;
@GuardedBy("mLock")
@@ -175,72 +168,16 @@ public final class MediaProjectionManagerService extends SystemService
mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mMediaRouterCallback = new MediaRouterCallback();
mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(context);
- mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mKeyguardManager.addKeyguardLockedStateListener(
- mContext.getMainExecutor(), this::onKeyguardLockedStateChanged);
- mRoleManager = mContext.getSystemService(RoleManager.class);
+ mMediaProjectionStopController = new MediaProjectionStopController(context,
+ this::maybeStopMediaProjection);
Watchdog.getInstance().addMonitor(this);
}
- /**
- * In order to record the keyguard, the MediaProjection package must be either:
- * - a holder of RECORD_SENSITIVE_CONTENT permission, or
- * - be one of the bugreport allowlisted packages, or
- * - hold the OP_PROJECT_MEDIA AppOp.
- */
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- private boolean canCaptureKeyguard() {
- if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
- return true;
- }
- synchronized (mLock) {
- if (mProjectionGrant == null || mProjectionGrant.packageName == null) {
- return false;
- }
- boolean disableScreenShareProtections = Settings.Global.getInt(
- getContext().getContentResolver(),
- DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
- if (disableScreenShareProtections) {
- Slog.v(TAG,
- "Allowing keyguard capture as screenshare protections are disabled.");
- return true;
- }
-
- if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT,
- mProjectionGrant.packageName)
- == PackageManager.PERMISSION_GRANTED) {
- Slog.v(TAG,
- "Allowing keyguard capture for package with RECORD_SENSITIVE_CONTENT "
- + "permission");
- return true;
- }
- if (AppOpsManager.MODE_ALLOWED == mAppOps.noteOpNoThrow(AppOpsManager.OP_PROJECT_MEDIA,
- mProjectionGrant.uid, mProjectionGrant.packageName, /* attributionTag= */ null,
- "recording lockscreen")) {
- // Some tools use media projection by granting the OP_PROJECT_MEDIA app
- // op via a shell command. Those tools can be granted keyguard capture
- Slog.v(TAG,
- "Allowing keyguard capture for package with OP_PROJECT_MEDIA AppOp ");
- return true;
- }
- if (isProjectionAppHoldingAppStreamingRoleLocked()) {
- Slog.v(TAG,
- "Allowing keyguard capture for package holding app streaming role.");
- return true;
- }
- return SystemConfig.getInstance().getBugreportWhitelistedPackages()
- .contains(mProjectionGrant.packageName);
- }
- }
-
- @VisibleForTesting
- void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
- if (!isKeyguardLocked) return;
+ private void maybeStopMediaProjection(int reason) {
synchronized (mLock) {
- if (mProjectionGrant != null && !canCaptureKeyguard()
- && mProjectionGrant.mVirtualDisplayId != INVALID_DISPLAY) {
- Slog.d(TAG, "Content Recording: Stopped MediaProjection"
- + " due to keyguard lock");
+ if (!mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant)) {
+ Slog.d(TAG, "Content Recording: Stopping MediaProjection due to "
+ + MediaProjectionStopController.stopReasonToString(reason));
mProjectionGrant.stop();
}
}
@@ -310,6 +247,8 @@ public final class MediaProjectionManagerService extends SystemService
}
});
}
+
+ mMediaProjectionStopController.startTrackingStopReasons(mContext);
}
@Override
@@ -736,20 +675,6 @@ public final class MediaProjectionManagerService extends SystemService
}
}
- /**
- * Application holding the app streaming role
- * ({@value AssociationRequest#DEVICE_PROFILE_APP_STREAMING}) are allowed to record the
- * lockscreen.
- *
- * @return true if the is held by the recording application.
- */
- @GuardedBy("mLock")
- private boolean isProjectionAppHoldingAppStreamingRoleLocked() {
- return mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
- mContext.getUser())
- .contains(mProjectionGrant.packageName);
- }
-
private void dump(final PrintWriter pw) {
pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
synchronized (mLock) {
@@ -957,18 +882,19 @@ public final class MediaProjectionManagerService extends SystemService
public void requestConsentForInvalidProjection(@NonNull IMediaProjection projection) {
requestConsentForInvalidProjection_enforcePermission();
- if (android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()
- && mKeyguardManager.isKeyguardLocked()) {
- Slog.v(TAG, "Reusing token: Won't request consent while the keyguard is locked");
- return;
- }
-
synchronized (mLock) {
if (!isCurrentProjection(projection)) {
Slog.v(TAG, "Reusing token: Won't request consent again for a token that "
+ "isn't current");
return;
}
+
+ if (mMediaProjectionStopController.isStartForbidden(mProjectionGrant)) {
+ Slog.v(TAG,
+ "Reusing token: Won't request consent while MediaProjection is "
+ + "restricted");
+ return;
+ }
}
// Remove calling app identity before performing any privileged operations.
@@ -1076,7 +1002,6 @@ public final class MediaProjectionManagerService extends SystemService
}
}
- @VisibleForTesting
final class MediaProjection extends IMediaProjection.Stub {
// Host app has 5 minutes to begin using the token before it is invalid.
// Some apps show a dialog for the user to interact with (selecting recording resolution)
@@ -1381,12 +1306,15 @@ public final class MediaProjectionManagerService extends SystemService
@Override
public void notifyVirtualDisplayCreated(int displayId) {
notifyVirtualDisplayCreated_enforcePermission();
- if (mKeyguardManager.isKeyguardLocked() && !canCaptureKeyguard()) {
- Slog.w(TAG, "Content Recording: Keyguard locked, aborting MediaProjection");
- stop();
- return;
- }
synchronized (mLock) {
+ if (mMediaProjectionStopController.isStartForbidden(mProjectionGrant)) {
+ Slog.w(TAG,
+ "Content Recording: MediaProjection start disallowed, aborting "
+ + "MediaProjection");
+ stop();
+ return;
+ }
+
mVirtualDisplayId = displayId;
// If prior session was does not have a valid display id, then update the display
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
new file mode 100644
index 000000000000..f5b26c41015e
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
+import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
+
+import android.app.AppOpsManager;
+import android.app.KeyguardManager;
+import android.app.role.RoleManager;
+import android.companion.AssociationRequest;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.provider.Settings;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+import android.view.Display;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemConfig;
+
+import java.util.function.Consumer;
+
+/**
+ * Tracks events that should cause MediaProjection to stop
+ */
+public class MediaProjectionStopController {
+
+ private static final String TAG = "MediaProjectionStopController";
+ @VisibleForTesting
+ static final int STOP_REASON_KEYGUARD = 1;
+ @VisibleForTesting
+ static final int STOP_REASON_CALL_END = 2;
+
+ private final TelephonyCallback mTelephonyCallback = new ProjectionTelephonyCallback();
+ private final Consumer<Integer> mStopReasonConsumer;
+ private final KeyguardManager mKeyguardManager;
+ private final TelecomManager mTelecomManager;
+ private final TelephonyManager mTelephonyManager;
+ private final AppOpsManager mAppOpsManager;
+ private final PackageManager mPackageManager;
+ private final RoleManager mRoleManager;
+ private final ContentResolver mContentResolver;
+
+ private boolean mIsInCall;
+
+ public MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer) {
+ mStopReasonConsumer = stopReasonConsumer;
+ mKeyguardManager = context.getSystemService(KeyguardManager.class);
+ mTelecomManager = context.getSystemService(TelecomManager.class);
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+ mPackageManager = context.getPackageManager();
+ mRoleManager = context.getSystemService(RoleManager.class);
+ mContentResolver = context.getContentResolver();
+ }
+
+ /**
+ * Start tracking stop reasons that may interrupt a MediaProjection session.
+ */
+ public void startTrackingStopReasons(Context context) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mKeyguardManager.addKeyguardLockedStateListener(context.getMainExecutor(),
+ this::onKeyguardLockedStateChanged);
+ if (com.android.media.projection.flags.Flags.stopMediaProjectionOnCallEnd()) {
+ callStateChanged();
+ mTelephonyManager.registerTelephonyCallback(context.getMainExecutor(),
+ mTelephonyCallback);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Checks whether the given projection grant is exempt from stopping restrictions.
+ */
+ public boolean isExemptFromStopping(
+ MediaProjectionManagerService.MediaProjection projectionGrant) {
+ return isExempt(projectionGrant, false);
+ }
+
+ /**
+ * Apps may disregard recording restrictions via MediaProjection for any stop reason if:
+ * - the "Disable Screenshare protections" developer option is enabled
+ * - the app is a holder of RECORD_SENSITIVE_CONTENT permission
+ * - the app holds the OP_PROJECT_MEDIA AppOp
+ * - the app holds the COMPANION_DEVICE_APP_STREAMING role
+ * - the app is one of the bugreport allowlisted packages
+ * - the current projection does not have an active VirtualDisplay associated with the
+ * MediaProjection session
+ */
+ private boolean isExempt(
+ MediaProjectionManagerService.MediaProjection projectionGrant, boolean forStart) {
+ if (projectionGrant == null || projectionGrant.packageName == null) {
+ return true;
+ }
+ boolean disableScreenShareProtections = Settings.Global.getInt(mContentResolver,
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
+ if (disableScreenShareProtections) {
+ Slog.v(TAG, "Continuing MediaProjection as screenshare protections are disabled.");
+ return true;
+ }
+
+ if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT, projectionGrant.packageName)
+ == PackageManager.PERMISSION_GRANTED) {
+ Slog.v(TAG,
+ "Continuing MediaProjection for package with RECORD_SENSITIVE_CONTENT "
+ + "permission");
+ return true;
+ }
+ if (AppOpsManager.MODE_ALLOWED == mAppOpsManager.noteOpNoThrow(
+ AppOpsManager.OP_PROJECT_MEDIA, projectionGrant.uid,
+ projectionGrant.packageName, /* attributionTag= */ null, "recording lockscreen")) {
+ // Some tools use media projection by granting the OP_PROJECT_MEDIA app
+ // op via a shell command.
+ Slog.v(TAG, "Continuing MediaProjection for package with OP_PROJECT_MEDIA AppOp ");
+ return true;
+ }
+ if (mRoleManager.getRoleHoldersAsUser(AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+ projectionGrant.userHandle).contains(projectionGrant.packageName)) {
+ Slog.v(TAG, "Continuing MediaProjection for package holding app streaming role.");
+ return true;
+ }
+ if (SystemConfig.getInstance().getBugreportWhitelistedPackages().contains(
+ projectionGrant.packageName)) {
+ Slog.v(TAG, "Continuing MediaProjection for package allowlisted for bugreporting.");
+ return true;
+ }
+ if (!forStart && projectionGrant.getVirtualDisplayId() == Display.INVALID_DISPLAY) {
+ Slog.v(TAG, "Continuing MediaProjection as current projection has no VirtualDisplay.");
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return {@code true} if a MediaProjection session is currently in a restricted state.
+ */
+ public boolean isStartForbidden(
+ MediaProjectionManagerService.MediaProjection projectionGrant) {
+ if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
+ return false;
+ }
+
+ if (!mKeyguardManager.isKeyguardLocked()) {
+ return false;
+ }
+
+ if (isExempt(projectionGrant, true)) {
+ return false;
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
+ if (!isKeyguardLocked) return;
+ if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
+ return;
+ }
+ mStopReasonConsumer.accept(STOP_REASON_KEYGUARD);
+ }
+
+ @VisibleForTesting
+ void callStateChanged() {
+ if (!com.android.media.projection.flags.Flags.stopMediaProjectionOnCallEnd()) {
+ return;
+ }
+ boolean isInCall = mTelecomManager.isInCall();
+ if (isInCall == mIsInCall) {
+ return;
+ }
+ if (mIsInCall && !isInCall) {
+ mStopReasonConsumer.accept(STOP_REASON_CALL_END);
+ }
+ mIsInCall = isInCall;
+ }
+
+ /**
+ * @return a String representation of the stop reason interrupting MediaProjection.
+ */
+ public static String stopReasonToString(int stopReason) {
+ switch (stopReason) {
+ case STOP_REASON_KEYGUARD -> {
+ return "STOP_REASON_KEYGUARD";
+ }
+ case STOP_REASON_CALL_END -> {
+ return "STOP_REASON_CALL_END";
+ }
+ }
+ return "";
+ }
+
+ private final class ProjectionTelephonyCallback extends TelephonyCallback implements
+ TelephonyCallback.CallStateListener {
+ @Override
+ public void onCallStateChanged(int state) {
+ callStateChanged();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index c5c8a5ea9d82..1f8a2007a9ff 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -262,7 +262,7 @@ public class MediaQualityService extends SystemService {
// TODO: implement
}
@Override
- public SoundProfile getSoundProfileById(String id) {
+ public SoundProfile getSoundProfile(int type, String id) {
return null;
}
@Override
@@ -313,6 +313,15 @@ public class MediaQualityService extends SystemService {
}
@Override
+ public List<String> getSoundProfileAllowList() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public void setSoundProfileAllowList(List<String> packages) {
+ }
+
+ @Override
public boolean isSupported() {
return false;
}
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 5914dbe44b0b..7fd962003ce9 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -92,10 +92,16 @@ public class GroupHelper {
static final int REGROUP_REASON_CHANNEL_UPDATE = 0;
// Regrouping needed because of notification bundling
static final int REGROUP_REASON_BUNDLE = 1;
+ // Regrouping needed because of notification unbundling
+ static final int REGROUP_REASON_UNBUNDLE = 2;
+ // Regrouping needed because of notification unbundling + the original group summary exists
+ static final int REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP = 3;
@IntDef(prefix = { "REGROUP_REASON_" }, value = {
REGROUP_REASON_CHANNEL_UPDATE,
REGROUP_REASON_BUNDLE,
+ REGROUP_REASON_UNBUNDLE,
+ REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP,
})
@Retention(RetentionPolicy.SOURCE)
@interface RegroupingReason {}
@@ -103,7 +109,6 @@ public class GroupHelper {
private final Callback mCallback;
private final int mAutoGroupAtCount;
private final int mAutogroupSparseGroupsAtCount;
- private final int mAutoGroupRegroupingAtCount;
private final Context mContext;
private final PackageManager mPackageManager;
private boolean mIsTestHarnessExempted;
@@ -190,11 +195,6 @@ public class GroupHelper {
mContext = context;
mPackageManager = packageManager;
mAutogroupSparseGroupsAtCount = autoGroupSparseGroupsAtCount;
- if (notificationRegroupOnClassification()) {
- mAutoGroupRegroupingAtCount = 1;
- } else {
- mAutoGroupRegroupingAtCount = mAutoGroupAtCount;
- }
NOTIFICATION_SHADE_SECTIONS = getNotificationShadeSections();
}
@@ -797,7 +797,8 @@ public class GroupHelper {
Slog.v(TAG, "isGroupChildInDifferentBundleThanSummary: " + record);
}
moveNotificationsToNewSection(record.getUserId(), pkgName,
- List.of(new NotificationMoveOp(record, null, fullAggregateGroupKey)));
+ List.of(new NotificationMoveOp(record, null, fullAggregateGroupKey)),
+ REGROUP_REASON_BUNDLE);
return;
}
}
@@ -945,6 +946,27 @@ public class GroupHelper {
}
}
+ /**
+ * Called when a notification that was classified (bundled) is restored to its original channel.
+ * The notification will be restored to its original group, if any/if summary still exists.
+ * Otherwise it will be moved to the appropriate section as an ungrouped notification.
+ *
+ * @param record the notification which had its channel updated
+ * @param originalSummaryExists the original group summary exists
+ */
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void onNotificationUnbundled(final NotificationRecord record,
+ final boolean originalSummaryExists) {
+ synchronized (mAggregatedNotifications) {
+ ArrayMap<String, NotificationRecord> notificationsToCheck = new ArrayMap<>();
+ notificationsToCheck.put(record.getKey(), record);
+ regroupNotifications(record.getUserId(), record.getSbn().getPackageName(),
+ notificationsToCheck,
+ originalSummaryExists ? REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP
+ : REGROUP_REASON_UNBUNDLE);
+ }
+ }
+
@GuardedBy("mAggregatedNotifications")
private void regroupNotifications(int userId, String pkgName,
ArrayMap<String, NotificationRecord> notificationsToCheck,
@@ -973,7 +995,7 @@ public class GroupHelper {
// Batch move to new section
if (!notificationsToMove.isEmpty()) {
- moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
+ moveNotificationsToNewSection(userId, pkgName, notificationsToMove, regroupingReason);
}
}
@@ -1093,7 +1115,7 @@ public class GroupHelper {
@GuardedBy("mAggregatedNotifications")
private void moveNotificationsToNewSection(final int userId, final String pkgName,
- final List<NotificationMoveOp> notificationsToMove) {
+ final List<NotificationMoveOp> notificationsToMove, int regroupingReason) {
record GroupUpdateOp(FullyQualifiedGroupKey groupKey, NotificationRecord record,
boolean hasSummary) { }
// Bundled operations to apply to groups affected by the channel update
@@ -1111,7 +1133,8 @@ public class GroupHelper {
if (DEBUG) {
Log.i(TAG,
"moveNotificationToNewSection: " + record + " " + newFullAggregateGroupKey
- + " from: " + oldFullAggregateGroupKey);
+ + " from: " + oldFullAggregateGroupKey + " regroupingReason: "
+ + regroupingReason);
}
// Update/remove aggregate summary for old group
@@ -1140,28 +1163,35 @@ public class GroupHelper {
// Add moved notifications to the ungrouped list for new group and do grouping
// after all notifications have been handled
if (newFullAggregateGroupKey != null) {
- final ArrayMap<String, NotificationAttributes> newAggregatedNotificationsAttrs =
+ if (notificationRegroupOnClassification()
+ && regroupingReason == REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP) {
+ // Just reset override group key, original summary exists
+ // => will be grouped back to its original group
+ record.setOverrideGroupKey(null);
+ } else {
+ final ArrayMap<String, NotificationAttributes> newAggregatedNotificationsAttrs =
mAggregatedNotifications.getOrDefault(newFullAggregateGroupKey,
new ArrayMap<>());
- boolean hasSummary = !newAggregatedNotificationsAttrs.isEmpty();
- ArrayMap<String, NotificationAttributes> ungrouped =
+ boolean hasSummary = !newAggregatedNotificationsAttrs.isEmpty();
+ ArrayMap<String, NotificationAttributes> ungrouped =
mUngroupedAbuseNotifications.getOrDefault(newFullAggregateGroupKey,
new ArrayMap<>());
- ungrouped.put(record.getKey(), new NotificationAttributes(
+ ungrouped.put(record.getKey(), new NotificationAttributes(
record.getFlags(),
record.getNotification().getSmallIcon(),
record.getNotification().color,
record.getNotification().visibility,
record.getNotification().getGroupAlertBehavior(),
record.getChannel().getId()));
- mUngroupedAbuseNotifications.put(newFullAggregateGroupKey, ungrouped);
+ mUngroupedAbuseNotifications.put(newFullAggregateGroupKey, ungrouped);
- record.setOverrideGroupKey(null);
+ record.setOverrideGroupKey(null);
- // Only add once, for triggering notification
- if (!groupsToUpdate.containsKey(newFullAggregateGroupKey)) {
- groupsToUpdate.put(newFullAggregateGroupKey,
- new GroupUpdateOp(newFullAggregateGroupKey, record, hasSummary));
+ // Only add once, for triggering notification
+ if (!groupsToUpdate.containsKey(newFullAggregateGroupKey)) {
+ groupsToUpdate.put(newFullAggregateGroupKey,
+ new GroupUpdateOp(newFullAggregateGroupKey, record, hasSummary));
+ }
}
}
}
@@ -1176,7 +1206,7 @@ public class GroupHelper {
NotificationRecord triggeringNotification = groupsToUpdate.get(groupKey).record;
boolean hasSummary = groupsToUpdate.get(groupKey).hasSummary;
//Group needs to be created/updated
- if (ungrouped.size() >= mAutoGroupRegroupingAtCount
+ if (ungrouped.size() >= mAutoGroupAtCount
|| (hasSummary && !aggregatedNotificationsAttrs.isEmpty())) {
NotificationSectioner sectioner = getSection(triggeringNotification);
if (sectioner == null) {
@@ -1436,7 +1466,8 @@ public class GroupHelper {
}
}
- private ArrayMap<String, NotificationRecord> getSparseGroups(
+ @VisibleForTesting
+ protected ArrayMap<String, NotificationRecord> getSparseGroups(
final FullyQualifiedGroupKey fullAggregateGroupKey,
final List<NotificationRecord> notificationList,
final Map<String, NotificationRecord> summaryByGroupKey,
@@ -1448,8 +1479,8 @@ public class GroupHelper {
&& summary.getUserId() == fullAggregateGroupKey.userId
&& summary.getSbn().isAppGroup()
&& !summary.getGroupKey().equals(fullAggregateGroupKey.toString())) {
- int numChildren = getNumChildrenForGroup(summary.getSbn().getGroup(),
- notificationList);
+ int numChildren = getNumChildrenForGroupWithSection(summary.getSbn().getGroup(),
+ notificationList, sectioner);
if (numChildren > 0 && numChildren < MIN_CHILD_COUNT_TO_AVOID_FORCE_GROUPING) {
sparseGroups.put(summary.getGroupKey(), summary);
}
@@ -1459,6 +1490,43 @@ public class GroupHelper {
return sparseGroups;
}
+ /**
+ * Get the number of children of a group if all match a certain section.
+ * Used for force grouping sparse groups, where the summary may match a section but the
+ * child notifications do not: ie. conversations
+ *
+ * @param groupKey the group key (name)
+ * @param notificationList all notifications list
+ * @param sectioner the section to match
+ * @return number of children in that group or -1 if section does not match
+ */
+ private int getNumChildrenForGroupWithSection(final String groupKey,
+ final List<NotificationRecord> notificationList,
+ final NotificationSectioner sectioner) {
+ int numChildren = 0;
+ for (NotificationRecord r : notificationList) {
+ if (!r.getNotification().isGroupSummary() && groupKey.equals(r.getSbn().getGroup())) {
+ NotificationSectioner childSection = getSection(r);
+ if (childSection == null || childSection != sectioner) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "getNumChildrenForGroupWithSection skip because invalid section: "
+ + groupKey + " r: " + r);
+ }
+ return -1;
+ } else {
+ numChildren++;
+ }
+ }
+ }
+
+ if (DEBUG) {
+ Slog.i(TAG,
+ "getNumChildrenForGroupWithSection " + groupKey + " numChild: " + numChildren);
+ }
+ return numChildren;
+ }
+
@GuardedBy("mAggregatedNotifications")
private void cacheCanceledSummary(NotificationRecord record) {
final FullyQualifiedGroupKey groupKey = new FullyQualifiedGroupKey(record.getUserId(),
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0d8880af2256..5182dfe60fcc 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7180,13 +7180,16 @@ public class NotificationManagerService extends SystemService {
Slog.i(TAG, "Removing app summary (all children bundled): "
+ groupSummary);
}
- canceledSummary = groupSummary;
- mSummaryByGroupKey.remove(oldGroupKey);
- cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(),
+ if (convertSummaryToNotificationLocked(groupSummary.getKey())) {
+ groupSummary.isCanceled = true;
+ canceledSummary = groupSummary;
+ mSummaryByGroupKey.remove(oldGroupKey);
+ cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(),
groupSummary.getSbn().getPackageName(),
groupSummary.getSbn().getTag(),
groupSummary.getSbn().getId(), 0, 0, false, groupSummary.getUserId(),
NotificationListenerService.REASON_GROUP_OPTIMIZATION, null);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 9f4b9f1c1f24..6d54be84d5e5 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -58,6 +58,7 @@ import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.provider.DeviceConfig;
import android.stats.storage.StorageEnums;
+import android.text.TextUtils;
import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
@@ -355,7 +356,8 @@ public final class BroadcastHelper {
@Nullable int[] userIds,
@Nullable int[] instantUserIds,
@Nullable SparseArray<int[]> broadcastAllowList,
- @NonNull AndroidPackage pkg) {
+ @NonNull AndroidPackage pkg,
+ @NonNull String[] sharedUidPackages) {
final boolean isForWholeApp = componentNames.contains(packageName);
if (isForWholeApp || !android.content.pm.Flags.reduceBroadcastsForComponentStateChanges()) {
sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, componentNames,
@@ -374,20 +376,36 @@ public final class BroadcastHelper {
exportedComponentNames.removeAll(notExportedComponentNames);
if (!notExportedComponentNames.isEmpty()) {
- // Limit sending of the PACKAGE_CHANGED broadcast to only the system and the
- // application itself when the component is not exported.
+ // Limit sending of the PACKAGE_CHANGED broadcast to only the system, the application
+ // itself and applications with the same UID when the component is not exported.
// First, send the PACKAGE_CHANGED broadcast to the system.
- sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
- notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
- broadcastAllowList, "android" /* targetPackageName */,
- new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+ if (!TextUtils.equals(packageName, "android")) {
+ sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+ notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
+ broadcastAllowList, "android" /* targetPackageName */,
+ new String[]{
+ PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+ }
// Second, send the PACKAGE_CHANGED broadcast to the application itself.
sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
broadcastAllowList, packageName /* targetPackageName */,
null /* requiredPermissions */);
+
+ // Third, send the PACKAGE_CHANGED broadcast to the applications with the same UID.
+ for (int i = 0; i < sharedUidPackages.length; i++) {
+ final String sharedPackage = sharedUidPackages[i];
+ if (TextUtils.equals(packageName, sharedPackage)) {
+ continue;
+ }
+ sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+ notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
+ broadcastAllowList, sharedPackage /* targetPackageName */,
+ null /* requiredPermissions */);
+ }
+
}
if (!exportedComponentNames.isEmpty()) {
@@ -936,7 +954,8 @@ public final class BroadcastHelper {
isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds);
mHandler.post(() -> sendPackageChangedBroadcastInternal(
packageName, dontKillApp, componentNames, packageUid, reason, userIds,
- instantUserIds, broadcastAllowList, setting.getPkg()));
+ instantUserIds, broadcastAllowList, setting.getPkg(),
+ snapshot.getSharedUserPackagesForPackage(packageName, userId)));
mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
packageUid, reason, userIds, instantUserIds, broadcastAllowList, mHandler);
}
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
index 745665bab5b3..527d68049537 100644
--- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -17,51 +17,240 @@
package com.android.server.pm;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+import static android.os.Process.SYSTEM_UID;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
import android.content.pm.SharedLibraryInfo;
+import android.content.pm.dependencyinstaller.DependencyInstallerCallback;
+import android.content.pm.dependencyinstaller.IDependencyInstallerCallback;
+import android.content.pm.dependencyinstaller.IDependencyInstallerService;
import android.content.pm.parsing.PackageLite;
+import android.os.Handler;
import android.os.OutcomeReceiver;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
import java.util.List;
+import java.util.concurrent.TimeUnit;
/**
* Helper class to interact with SDK Dependency Installer service.
*/
public class InstallDependencyHelper {
+ private static final String TAG = InstallDependencyHelper.class.getSimpleName();
+ private static final boolean DEBUG = true;
+ private static final String ACTION_INSTALL_DEPENDENCY =
+ "android.intent.action.INSTALL_DEPENDENCY";
+ // The maximum amount of time to wait before the system unbinds from the verifier.
+ private static final long UNBIND_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(6);
+ private static final long REQUEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(1);
+
private final SharedLibrariesImpl mSharedLibraries;
+ private final Context mContext;
+ private final Object mRemoteServiceLock = new Object();
+
+ @GuardedBy("mRemoteServiceLock")
+ private ServiceConnector<IDependencyInstallerService> mRemoteService = null;
- InstallDependencyHelper(SharedLibrariesImpl sharedLibraries) {
+ InstallDependencyHelper(Context context, SharedLibrariesImpl sharedLibraries) {
+ mContext = context;
mSharedLibraries = sharedLibraries;
}
- void resolveLibraryDependenciesIfNeeded(PackageLite pkg,
- OutcomeReceiver<Void, PackageManagerException> callback) {
- final List<SharedLibraryInfo> missing;
+ void resolveLibraryDependenciesIfNeeded(PackageLite pkg, Computer snapshot, int userId,
+ Handler handler, OutcomeReceiver<Void, PackageManagerException> origCallback) {
+ CallOnceProxy callback = new CallOnceProxy(handler, origCallback);
try {
- missing = mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
+ resolveLibraryDependenciesIfNeededInternal(pkg, snapshot, userId, handler, callback);
} catch (PackageManagerException e) {
callback.onError(e);
- return;
+ } catch (Exception e) {
+ onError(callback, e.getMessage());
}
+ }
+
+
+ private void resolveLibraryDependenciesIfNeededInternal(PackageLite pkg, Computer snapshot,
+ int userId, Handler handler, CallOnceProxy callback) throws PackageManagerException {
+ final List<SharedLibraryInfo> missing =
+ mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
if (missing.isEmpty()) {
+ if (DEBUG) {
+ Slog.i(TAG, "No missing dependency for " + pkg);
+ }
// No need for dependency resolution. Move to installation directly.
callback.onResult(null);
return;
}
- try {
- bindToDependencyInstaller();
- } catch (Exception e) {
- PackageManagerException pe = new PackageManagerException(
- INSTALL_FAILED_MISSING_SHARED_LIBRARY, e.getMessage());
- callback.onError(pe);
+ if (!bindToDependencyInstallerIfNeeded(userId, handler, snapshot)) {
+ onError(callback, "Dependency Installer Service not found");
+ return;
+ }
+
+ IDependencyInstallerCallback serviceCallback = new IDependencyInstallerCallback.Stub() {
+ @Override
+ public void onAllDependenciesResolved(int[] sessionIds) throws RemoteException {
+ // TODO(b/372862145): Implement waiting for sessions to finish installation
+ callback.onResult(null);
+ }
+
+ @Override
+ public void onFailureToResolveAllDependencies() throws RemoteException {
+ onError(callback, "Failed to resolve all dependencies automatically");
+ }
+ };
+
+ boolean scheduleSuccess;
+ synchronized (mRemoteServiceLock) {
+ scheduleSuccess = mRemoteService.run(service -> {
+ service.onDependenciesRequired(missing,
+ new DependencyInstallerCallback(serviceCallback.asBinder()));
+ });
+ }
+ if (!scheduleSuccess) {
+ onError(callback, "Failed to schedule job on Dependency Installer Service");
}
}
- private void bindToDependencyInstaller() {
- throw new IllegalStateException("Failed to bind to Dependency Installer");
+ private void onError(CallOnceProxy callback, String msg) {
+ PackageManagerException pe = new PackageManagerException(
+ INSTALL_FAILED_MISSING_SHARED_LIBRARY, msg);
+ callback.onError(pe);
}
+ private boolean bindToDependencyInstallerIfNeeded(int userId, Handler handler,
+ Computer snapshot) {
+ synchronized (mRemoteServiceLock) {
+ if (mRemoteService != null) {
+ if (DEBUG) {
+ Slog.i(TAG, "DependencyInstallerService already bound");
+ }
+ return true;
+ }
+ }
+
+ Intent serviceIntent = new Intent(ACTION_INSTALL_DEPENDENCY);
+ // TODO(b/372862145): Use RoleManager to find the package name
+ List<ResolveInfo> resolvedIntents = snapshot.queryIntentServicesInternal(
+ serviceIntent, /*resolvedType=*/ null, /*flags=*/0,
+ userId, SYSTEM_UID, Process.INVALID_PID,
+ /*includeInstantApps*/ false, /*resolveForStart*/ false);
+
+ if (resolvedIntents.isEmpty()) {
+ return false;
+ }
+
+ ResolveInfo resolveInfo = resolvedIntents.getFirst();
+ ComponentName componentName = resolveInfo.getComponentInfo().getComponentName();
+ serviceIntent.setComponent(componentName);
+
+ ServiceConnector<IDependencyInstallerService> serviceConnector =
+ new ServiceConnector.Impl<IDependencyInstallerService>(mContext, serviceIntent,
+ Context.BIND_AUTO_CREATE, userId,
+ IDependencyInstallerService.Stub::asInterface) {
+ @Override
+ protected Handler getJobHandler() {
+ return handler;
+ }
+
+ @Override
+ protected long getRequestTimeoutMs() {
+ return REQUEST_TIMEOUT_MILLIS;
+ }
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ return UNBIND_TIMEOUT_MILLIS;
+ }
+ };
+
+
+ synchronized (mRemoteServiceLock) {
+ // Some other thread managed to connect to the service first
+ if (mRemoteService != null) {
+ return true;
+ }
+ mRemoteService = serviceConnector;
+ mRemoteService.setServiceLifecycleCallbacks(
+ new ServiceConnector.ServiceLifecycleCallbacks<>() {
+ @Override
+ public void onDisconnected(@NonNull IDependencyInstallerService service) {
+ Slog.w(TAG,
+ "DependencyInstallerService " + componentName + " is disconnected");
+ destroy();
+ }
+
+ @Override
+ public void onBinderDied() {
+ Slog.w(TAG, "DependencyInstallerService " + componentName + " has died");
+ destroy();
+ }
+
+ private void destroy() {
+ synchronized (mRemoteServiceLock) {
+ if (mRemoteService != null) {
+ mRemoteService.unbind();
+ mRemoteService = null;
+ }
+ }
+ }
+
+ });
+ AndroidFuture<IDependencyInstallerService> unusedFuture = mRemoteService.connect();
+ }
+ return true;
+ }
+
+ /**
+ * Ensure we call one of the outcomes only once, on the right handler.
+ *
+ * Repeated calls will be no-op.
+ */
+ private static class CallOnceProxy implements OutcomeReceiver<Void, PackageManagerException> {
+ private final Handler mHandler;
+ private final OutcomeReceiver<Void, PackageManagerException> mCallback;
+ @GuardedBy("this")
+ private boolean mCalled = false;
+
+ CallOnceProxy(Handler handler, OutcomeReceiver<Void, PackageManagerException> callback) {
+ mHandler = handler;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResult(Void result) {
+ synchronized (this) {
+ if (!mCalled) {
+ mHandler.post(() -> {
+ mCallback.onResult(null);
+ });
+ mCalled = true;
+ }
+ }
+ }
+
+ @Override
+ public void onError(@NonNull PackageManagerException error) {
+ synchronized (this) {
+ if (!mCalled) {
+ mHandler.post(() -> {
+ mCallback.onError(error);
+ });
+ mCalled = true;
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index eb70748918b6..9b44f93467ed 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -347,7 +347,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
synchronized (mVerificationPolicyPerUser) {
mVerificationPolicyPerUser.put(USER_SYSTEM, DEFAULT_VERIFICATION_POLICY);
}
- mInstallDependencyHelper = new InstallDependencyHelper(
+ mInstallDependencyHelper = new InstallDependencyHelper(mContext,
mPm.mInjector.getSharedLibrariesImpl());
LocalServices.getService(SystemServiceManager.class).startService(
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index e156b31c19e1..505b7e6205df 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3428,8 +3428,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private void resolveLibraryDependenciesIfNeeded() {
synchronized (mLock) {
- // TODO(b/372862145): Callback should be called on a handler passed as parameter
mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(mPackageLite,
+ mPm.snapshotComputer(), userId, mHandler,
new OutcomeReceiver<>() {
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 7ef35829a46e..961b4b3c91e3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3606,6 +3606,13 @@ class PackageManagerShellCommand extends ShellCommand {
case "--force-verification":
sessionParams.setForceVerification();
break;
+ case "--disable-auto-install-dependencies":
+ if (Flags.sdkDependencyInstaller()) {
+ sessionParams.setEnableAutoInstallDependencies(false);
+ } else {
+ throw new IllegalArgumentException("Unknown option " + opt);
+ }
+ break;
default:
throw new IllegalArgumentException("Unknown option " + opt);
}
@@ -4894,6 +4901,10 @@ class PackageManagerShellCommand extends ShellCommand {
+ "#compiler_filters");
pw.println(" or 'skip'");
pw.println(" --force-verification: if set, enable the verification for this install");
+ if (Flags.sdkDependencyInstaller()) {
+ pw.println(" --disable-auto-install-dependencies: if set, any missing shared");
+ pw.println(" library dependencies will not be auto-installed");
+ }
pw.println("");
pw.println(" install-existing [--user USER_ID|all|current]");
pw.println(" [--instant] [--full] [--wait] [--restrict-permissions] PACKAGE");
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index fc54f6864db0..17d7a14d9129 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -1017,10 +1017,11 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
boolean isSdkOrStatic = libraryType.equals(LIBRARY_TYPE_SDK)
|| libraryType.equals(LIBRARY_TYPE_STATIC);
if (isSdkOrStatic && outMissingSharedLibraryInfos != null) {
- // TODO(b/372862145): Pass the CertDigest too
// If Dependency Installation is supported, try that instead of failing.
+ final List<String> libCertDigests = Arrays.asList(requiredCertDigests[i]);
SharedLibraryInfo missingLibrary = new SharedLibraryInfo(
- libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE
+ libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE,
+ libCertDigests
);
outMissingSharedLibraryInfos.add(missingLibrary);
} else {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 06e29c2c1408..b2b8aaf7dd2a 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -4984,7 +4984,10 @@ public class UserManagerService extends IUserManager.Stub {
res.getValue(com.android.internal.R.string.owner_name, mOwnerNameTypedValue, true);
final CharSequence ownerName = mOwnerNameTypedValue.coerceToString();
mOwnerName.set(ownerName != null ? ownerName.toString() : null);
+ // Invalidate when owners name changes due to config change.
+ UserManager.invalidateCacheOnUserDataChanged();
}
+
}
private void scheduleWriteUserList() {
@@ -4997,6 +5000,8 @@ public class UserManagerService extends IUserManager.Stub {
Message msg = mHandler.obtainMessage(WRITE_USER_LIST_MSG);
mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
}
+ // Invalidate cache when {@link UserData} changed, but write was scheduled for later.
+ UserManager.invalidateCacheOnUserDataChanged();
}
private void scheduleWriteUser(@UserIdInt int userId) {
@@ -5009,6 +5014,8 @@ public class UserManagerService extends IUserManager.Stub {
Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userId);
mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
}
+ // Invalidate cache when {@link Data} changed, but write was scheduled for later.
+ UserManager.invalidateCacheOnUserDataChanged();
}
private ResilientAtomicFile getUserFile(int userId) {
@@ -5032,6 +5039,9 @@ public class UserManagerService extends IUserManager.Stub {
if (DBG) {
debug("writeUserLP " + userData);
}
+ // invalidate caches related to any {@link UserData} change.
+ UserManager.invalidateCacheOnUserDataChanged();
+
try (ResilientAtomicFile userFile = getUserFile(userData.info.id)) {
FileOutputStream fos = null;
try {
@@ -5196,6 +5206,8 @@ public class UserManagerService extends IUserManager.Stub {
if (DBG) {
debug("writeUserList");
}
+ // invalidate caches related to any {@link UserData} change.
+ UserManager.invalidateCacheOnUserDataChanged();
try (ResilientAtomicFile file = getUserListFile()) {
FileOutputStream fos = null;
@@ -7958,7 +7970,7 @@ public class UserManagerService extends IUserManager.Stub {
Settings.Secure.getIntForUser(mContext.getContentResolver(),
HIDE_PRIVATESPACE_ENTRY_POINT, parentId) == 1);
} catch (Settings.SettingNotFoundException e) {
- throw new RuntimeException(e);
+ config.putBoolean(PRIVATE_SPACE_ENTRYPOINT_HIDDEN, false);
}
}
return new LauncherUserInfo.Builder(userDetails.getName(),
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 4f67318faddb..c9f66eb5ccb2 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -29,6 +29,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
import android.hardware.input.AppLaunchData;
@@ -65,6 +66,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -84,6 +86,7 @@ public class ModifierShortcutManager {
private static final String ATTRIBUTE_PACKAGE = "package";
private static final String ATTRIBUTE_CLASS = "class";
private static final String ATTRIBUTE_SHORTCUT = "shortcut";
+ private static final String ATTRIBUTE_KEYCODE = "keycode";
private static final String ATTRIBUTE_CATEGORY = "category";
private static final String ATTRIBUTE_SHIFT = "shift";
private static final String ATTRIBUTE_ROLE = "role";
@@ -167,6 +170,9 @@ public class ModifierShortcutManager {
}, UserHandle.ALL);
mCurrentUser = currentUser;
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
+ }
+
+ void onSystemReady() {
loadShortcuts();
}
@@ -335,6 +341,7 @@ public class ModifierShortcutManager {
try {
XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+ KeyCharacterMap virtualKcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
while (true) {
XmlUtils.nextElement(parser);
@@ -353,15 +360,36 @@ public class ModifierShortcutManager {
String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
String roleName = parser.getAttributeValue(null, ATTRIBUTE_ROLE);
+ final int keycode;
+ final int modifierState;
+ TypedArray a = mContext.getResources().obtainAttributes(parser,
+ R.styleable.Bookmark);
+ try {
+ keycode = a.getInt(R.styleable.Bookmark_keycode, KeyEvent.KEYCODE_UNKNOWN);
+ modifierState = a.getInt(R.styleable.Bookmark_modifierState, 0);
+ } finally {
+ a.recycle();
+ }
+ if (TextUtils.isEmpty(shortcutName) && keycode != KeyEvent.KEYCODE_UNKNOWN) {
+ // Try to find shortcutChar using keycode
+ shortcutName = String.valueOf(virtualKcm.getDisplayLabel(keycode)).toLowerCase(
+ Locale.ROOT);
+ }
if (TextUtils.isEmpty(shortcutName)) {
Log.w(TAG, "Shortcut required for bookmark with category=" + categoryName
+ " packageName=" + packageName + " className=" + className
- + " role=" + roleName + "shiftName=" + shiftName);
+ + " role=" + roleName + " shiftName=" + shiftName + " keycode= "
+ + keycode + " modifierState= " + modifierState);
continue;
}
- final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
+ final boolean isShiftShortcut;
+ if (!TextUtils.isEmpty(shiftName)) {
+ isShiftShortcut = shiftName.equals("true");
+ } else {
+ isShiftShortcut = (modifierState & KeyEvent.META_SHIFT_ON) != 0;
+ }
if (modifierShortcutManagerRefactor()) {
final char shortcutChar = shortcutName.charAt(0);
@@ -376,7 +404,7 @@ public class ModifierShortcutManager {
bookmark = new RoleBookmark(shortcutChar, isShiftShortcut, roleName);
}
if (bookmark != null) {
- Log.d(TAG, "adding shortcut " + bookmark + "shift="
+ Log.d(TAG, "adding shortcut " + bookmark + " shift="
+ isShiftShortcut + " char=" + shortcutChar);
mBookmarks.put(new Pair<>(shortcutChar, isShiftShortcut), bookmark);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index dda5bcf24d07..85e7cfe33c0e 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -6610,6 +6610,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// In normal flow, systemReady is called before other system services are ready.
// So it is better not to bind keyguard here.
mKeyguardDelegate.onSystemReady();
+ mModifierShortcutManager.onSystemReady();
mVrManagerInternal = LocalServices.getService(VrManagerInternal.class);
if (mVrManagerInternal != null) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 0acfe92f578d..37883f594227 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2332,6 +2332,8 @@ public final class PowerManagerService extends SystemService
Trace.traceBegin(Trace.TRACE_TAG_POWER, traceMethodName);
try {
// Phase 2: Handle wakefulness change and bookkeeping.
+ // Under lock, invalidate before set ensures caches won't return stale values.
+ mInjector.invalidateIsInteractiveCaches();
mWakefulnessRaw = newWakefulness;
mWakefulnessChanging = true;
mDirty |= DIRTY_WAKEFULNESS;
@@ -2429,7 +2431,6 @@ public final class PowerManagerService extends SystemService
void onPowerGroupEventLocked(int event, PowerGroup powerGroup) {
mWakefulnessChanging = true;
mDirty |= DIRTY_WAKEFULNESS;
- mInjector.invalidateIsInteractiveCaches();
final int groupId = powerGroup.getGroupId();
if (event == DisplayGroupPowerChangeListener.DISPLAY_GROUP_REMOVED) {
mPowerGroups.delete(groupId);
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 2c0ce252df18..17459df2bc1a 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -33,11 +33,15 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.hardware.power.ChannelConfig;
+import android.hardware.power.CpuHeadroomParams;
+import android.hardware.power.GpuHeadroomParams;
import android.hardware.power.IPower;
import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag;
import android.hardware.power.WorkDuration;
import android.os.Binder;
+import android.os.CpuHeadroomParamsInternal;
+import android.os.GpuHeadroomParamsInternal;
import android.os.Handler;
import android.os.IBinder;
import android.os.IHintManager;
@@ -90,6 +94,10 @@ public final class HintManagerService extends SystemService {
private static final int EVENT_CLEAN_UP_UID = 3;
@VisibleForTesting static final int CLEAN_UP_UID_DELAY_MILLIS = 1000;
+ private static final int DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS = 1000;
+ private static final int DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS = 1000;
+ private static final int HEADROOM_INTERVAL_UNSUPPORTED = -1;
+ @VisibleForTesting static final int DEFAULT_HEADROOM_PID = -1;
@VisibleForTesting final long mHintSessionPreferredRate;
@@ -160,10 +168,76 @@ public final class HintManagerService extends SystemService {
private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint";
private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
+ private static final String PROPERTY_USE_HAL_HEADROOMS = "persist.hms.use_hal_headrooms";
private Boolean mFMQUsesIntegratedEventFlag = false;
- @VisibleForTesting final IHintManager.Stub mService = new BinderService();
+ private final Object mCpuHeadroomLock = new Object();
+
+ private static class CpuHeadroomCacheItem {
+ long mExpiredTimeMillis;
+ CpuHeadroomParamsInternal mParams;
+ float[] mHeadroom;
+ long mPid;
+
+ CpuHeadroomCacheItem(long expiredTimeMillis, CpuHeadroomParamsInternal params,
+ float[] headroom, long pid) {
+ mExpiredTimeMillis = expiredTimeMillis;
+ mParams = params;
+ mPid = pid;
+ mHeadroom = headroom;
+ }
+
+ private boolean match(CpuHeadroomParamsInternal params, long pid) {
+ if (mParams == null && params == null) return true;
+ if (mParams != null) {
+ return mParams.equals(params) && pid == mPid;
+ }
+ return false;
+ }
+
+ private boolean isExpired() {
+ return System.currentTimeMillis() > mExpiredTimeMillis;
+ }
+ }
+
+ @GuardedBy("mCpuHeadroomLock")
+ private final List<CpuHeadroomCacheItem> mCpuHeadroomCache;
+ private final long mCpuHeadroomIntervalMillis;
+
+ private final Object mGpuHeadroomLock = new Object();
+
+ private static class GpuHeadroomCacheItem {
+ long mExpiredTimeMillis;
+ GpuHeadroomParamsInternal mParams;
+ float mHeadroom;
+
+ GpuHeadroomCacheItem(long expiredTimeMillis, GpuHeadroomParamsInternal params,
+ float headroom) {
+ mExpiredTimeMillis = expiredTimeMillis;
+ mParams = params;
+ mHeadroom = headroom;
+ }
+
+ private boolean match(GpuHeadroomParamsInternal params) {
+ if (mParams == null && params == null) return true;
+ if (mParams != null) {
+ return mParams.equals(params);
+ }
+ return false;
+ }
+
+ private boolean isExpired() {
+ return System.currentTimeMillis() > mExpiredTimeMillis;
+ }
+ }
+
+ @GuardedBy("mGpuHeadroomLock")
+ private final List<GpuHeadroomCacheItem> mGpuHeadroomCache;
+ private final long mGpuHeadroomIntervalMillis;
+
+ @VisibleForTesting
+ final IHintManager.Stub mService = new BinderService();
public HintManagerService(Context context) {
this(context, new Injector());
@@ -197,13 +271,72 @@ public final class HintManagerService extends SystemService {
mPowerHal = injector.createIPower();
mPowerHalVersion = 0;
mUsesFmq = false;
+ long cpuHeadroomIntervalMillis = HEADROOM_INTERVAL_UNSUPPORTED;
+ long gpuHeadroomIntervalMillis = HEADROOM_INTERVAL_UNSUPPORTED;
if (mPowerHal != null) {
try {
mPowerHalVersion = mPowerHal.getInterfaceVersion();
+ if (mPowerHal.getInterfaceVersion() >= 6) {
+ if (SystemProperties.getBoolean(PROPERTY_USE_HAL_HEADROOMS, true)) {
+ cpuHeadroomIntervalMillis = checkCpuHeadroomSupport();
+ gpuHeadroomIntervalMillis = checkGpuHeadroomSupport();
+ }
+ }
} catch (RemoteException e) {
throw new IllegalStateException("Could not contact PowerHAL!", e);
}
}
+ mCpuHeadroomIntervalMillis = cpuHeadroomIntervalMillis;
+ mGpuHeadroomIntervalMillis = gpuHeadroomIntervalMillis;
+ if (mCpuHeadroomIntervalMillis > 0) {
+ mCpuHeadroomCache = new ArrayList<>(4);
+ } else {
+ mCpuHeadroomCache = null;
+ }
+ if (mGpuHeadroomIntervalMillis > 0) {
+ mGpuHeadroomCache = new ArrayList<>(2);
+ } else {
+ mGpuHeadroomCache = null;
+ }
+ }
+
+ private long checkCpuHeadroomSupport() {
+ try {
+ synchronized (mCpuHeadroomLock) {
+ final CpuHeadroomParams defaultParams = new CpuHeadroomParams();
+ defaultParams.pid = Process.myPid();
+ float[] ret = mPowerHal.getCpuHeadroom(defaultParams);
+ if (ret != null && ret.length > 0) {
+ return Math.max(
+ DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS,
+ mPowerHal.getCpuHeadroomMinIntervalMillis());
+ }
+ }
+
+ } catch (UnsupportedOperationException e) {
+ Slog.w(TAG, "getCpuHeadroom HAL API is not supported", e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "getCpuHeadroom HAL API fails, disabling the API", e);
+ }
+ return HEADROOM_INTERVAL_UNSUPPORTED;
+ }
+
+ private long checkGpuHeadroomSupport() {
+ try {
+ synchronized (mGpuHeadroomLock) {
+ float ret = mPowerHal.getGpuHeadroom(new GpuHeadroomParams());
+ if (!Float.isNaN(ret)) {
+ return Math.max(
+ DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS,
+ mPowerHal.getGpuHeadroomMinIntervalMillis());
+ }
+ }
+ } catch (UnsupportedOperationException e) {
+ Slog.w(TAG, "getGpuHeadroom HAL API is not supported", e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "getGpuHeadroom HAL API fails, disabling the API", e);
+ }
+ return HEADROOM_INTERVAL_UNSUPPORTED;
}
private ServiceThread createCleanUpThread() {
@@ -738,7 +871,7 @@ public final class HintManagerService extends SystemService {
mLinked = false;
}
if (mConfig != null) {
- try {
+ try {
mPowerHal.closeSessionChannel(mTgid, mUid);
} catch (RemoteException e) {
throw new IllegalStateException("Failed to close session channel!", e);
@@ -982,13 +1115,13 @@ public final class HintManagerService extends SystemService {
}
// returns the first invalid tid or null if not found
- private Integer checkTidValid(int uid, int tgid, int [] tids, IntArray nonIsolated) {
+ private Integer checkTidValid(int uid, int tgid, int[] tids, IntArray nonIsolated) {
// Make sure all tids belongs to the same UID (including isolated UID),
// tids can belong to different application processes.
List<Integer> isolatedPids = null;
for (int i = 0; i < tids.length; i++) {
int tid = tids[i];
- final String[] procStatusKeys = new String[] {
+ final String[] procStatusKeys = new String[]{
"Uid:",
"Tgid:"
};
@@ -1058,7 +1191,7 @@ public final class HintManagerService extends SystemService {
Slogf.w(TAG, errMsg);
throw new SecurityException(errMsg);
}
- if (resetOnForkEnabled()){
+ if (resetOnForkEnabled()) {
try {
for (int tid : tids) {
int policy = Process.getThreadScheduler(tid);
@@ -1214,6 +1347,124 @@ public final class HintManagerService extends SystemService {
}
@Override
+ public float[] getCpuHeadroom(@Nullable CpuHeadroomParamsInternal params) {
+ if (mCpuHeadroomIntervalMillis <= 0) {
+ throw new UnsupportedOperationException();
+ }
+ CpuHeadroomParams halParams = new CpuHeadroomParams();
+ halParams.pid = Binder.getCallingPid();
+ if (params != null) {
+ halParams.calculationType = params.calculationType;
+ halParams.selectionType = params.selectionType;
+ if (params.usesDeviceHeadroom) {
+ halParams.pid = DEFAULT_HEADROOM_PID;
+ }
+ }
+ synchronized (mCpuHeadroomLock) {
+ while (!mCpuHeadroomCache.isEmpty()) {
+ if (mCpuHeadroomCache.getFirst().isExpired()) {
+ mCpuHeadroomCache.removeFirst();
+ } else {
+ break;
+ }
+ }
+ for (int i = 0; i < mCpuHeadroomCache.size(); ++i) {
+ final CpuHeadroomCacheItem item = mCpuHeadroomCache.get(i);
+ if (item.match(params, halParams.pid)) {
+ item.mExpiredTimeMillis =
+ System.currentTimeMillis() + mCpuHeadroomIntervalMillis;
+ mCpuHeadroomCache.remove(i);
+ mCpuHeadroomCache.add(item);
+ return item.mHeadroom;
+ }
+ }
+ }
+ // return from HAL directly
+ try {
+ float[] headroom = mPowerHal.getCpuHeadroom(halParams);
+ if (headroom == null || headroom.length == 0) {
+ Slog.wtf(TAG,
+ "CPU headroom from Power HAL is invalid: " + Arrays.toString(headroom));
+ return new float[]{Float.NaN};
+ }
+ synchronized (mCpuHeadroomLock) {
+ mCpuHeadroomCache.add(new CpuHeadroomCacheItem(
+ System.currentTimeMillis() + mCpuHeadroomIntervalMillis,
+ params, headroom, halParams.pid
+ ));
+ }
+ return headroom;
+
+ } catch (RemoteException e) {
+ return new float[]{Float.NaN};
+ }
+ }
+
+ @Override
+ public float getGpuHeadroom(@Nullable GpuHeadroomParamsInternal params) {
+ if (mGpuHeadroomIntervalMillis <= 0) {
+ throw new UnsupportedOperationException();
+ }
+ GpuHeadroomParams halParams = new GpuHeadroomParams();
+ if (params != null) {
+ halParams.calculationType = params.calculationType;
+ }
+ synchronized (mGpuHeadroomLock) {
+ while (!mGpuHeadroomCache.isEmpty()) {
+ if (mGpuHeadroomCache.getFirst().isExpired()) {
+ mGpuHeadroomCache.removeFirst();
+ } else {
+ break;
+ }
+ }
+ for (int i = 0; i < mGpuHeadroomCache.size(); ++i) {
+ final GpuHeadroomCacheItem item = mGpuHeadroomCache.get(i);
+ if (item.match(params)) {
+ item.mExpiredTimeMillis =
+ System.currentTimeMillis() + mGpuHeadroomIntervalMillis;
+ mGpuHeadroomCache.remove(i);
+ mGpuHeadroomCache.add(item);
+ return item.mHeadroom;
+ }
+ }
+ }
+ // return from HAL directly
+ try {
+ float headroom = mPowerHal.getGpuHeadroom(halParams);
+ if (Float.isNaN(headroom)) {
+ Slog.wtf(TAG,
+ "GPU headroom from Power HAL is NaN");
+ return Float.NaN;
+ }
+ synchronized (mGpuHeadroomLock) {
+ mGpuHeadroomCache.add(new GpuHeadroomCacheItem(
+ System.currentTimeMillis() + mGpuHeadroomIntervalMillis,
+ params, headroom
+ ));
+ }
+ return headroom;
+ } catch (RemoteException e) {
+ return Float.NaN;
+ }
+ }
+
+ @Override
+ public long getCpuHeadroomMinIntervalMillis() throws RemoteException {
+ if (mCpuHeadroomIntervalMillis <= 0) {
+ throw new UnsupportedOperationException();
+ }
+ return mCpuHeadroomIntervalMillis;
+ }
+
+ @Override
+ public long getGpuHeadroomMinIntervalMillis() throws RemoteException {
+ if (mGpuHeadroomIntervalMillis <= 0) {
+ throw new UnsupportedOperationException();
+ }
+ return mGpuHeadroomIntervalMillis;
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
return;
@@ -1235,6 +1486,25 @@ public final class HintManagerService extends SystemService {
}
}
}
+ pw.println("CPU Headroom Interval: " + mCpuHeadroomIntervalMillis);
+ pw.println("GPU Headroom Interval: " + mGpuHeadroomIntervalMillis);
+ try {
+ CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal();
+ params.selectionType = CpuHeadroomParams.SelectionType.ALL;
+ params.usesDeviceHeadroom = true;
+ pw.println("CPU headroom: " + Arrays.toString(getCpuHeadroom(params)));
+ params = new CpuHeadroomParamsInternal();
+ params.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
+ params.usesDeviceHeadroom = true;
+ pw.println("CPU headroom per core: " + Arrays.toString(getCpuHeadroom(params)));
+ } catch (Exception e) {
+ pw.println("CPU headroom: N/A");
+ }
+ try {
+ pw.println("GPU headroom: " + getGpuHeadroom(null));
+ } catch (Exception e) {
+ pw.println("GPU headroom: N/A");
+ }
}
private void logPerformanceHintSessionAtom(int uid, long sessionId,
@@ -1467,7 +1737,7 @@ public final class HintManagerService extends SystemService {
Slogf.w(TAG, errMsg);
throw new SecurityException(errMsg);
}
- if (resetOnForkEnabled()){
+ if (resetOnForkEnabled()) {
try {
for (int tid : tids) {
int policy = Process.getThreadScheduler(tid);
diff --git a/services/core/java/com/android/server/security/forensic/BackupTransportConnection.java b/services/core/java/com/android/server/security/forensic/ForensicEventTransportConnection.java
index caca011b6549..b85199ed9218 100644
--- a/services/core/java/com/android/server/security/forensic/BackupTransportConnection.java
+++ b/services/core/java/com/android/server/security/forensic/ForensicEventTransportConnection.java
@@ -16,15 +16,19 @@
package com.android.server.security.forensic;
+import static android.Manifest.permission.BIND_FORENSIC_EVENT_TRANSPORT_SERVICE;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.security.forensic.ForensicEvent;
-import android.security.forensic.IBackupTransport;
+import android.security.forensic.IForensicEventTransport;
import android.text.TextUtils;
import android.util.Slog;
@@ -36,20 +40,20 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-public class BackupTransportConnection implements ServiceConnection {
- private static final String TAG = "BackupTransportConnection";
+public class ForensicEventTransportConnection implements ServiceConnection {
+ private static final String TAG = "ForensicEventTransportConnection";
private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 mins
private final Context mContext;
- private String mForensicBackupTransportConfig;
- volatile IBackupTransport mService;
+ private String mForensicEventTransportConfig;
+ volatile IForensicEventTransport mService;
- public BackupTransportConnection(Context context) {
+ public ForensicEventTransportConnection(Context context) {
mContext = context;
mService = null;
}
/**
- * Initialize the BackupTransport binder service.
+ * Initialize the ForensicEventTransport binder service.
* @return Whether the initialization succeed.
*/
public boolean initialize() {
@@ -74,7 +78,7 @@ public class BackupTransportConnection implements ServiceConnection {
}
/**
- * Add data to the BackupTransport binder service.
+ * Add data to the ForensicEventTransport binder service.
* @param data List of ForensicEvent.
* @return Whether the data is added to the binder service.
*/
@@ -109,21 +113,37 @@ public class BackupTransportConnection implements ServiceConnection {
return future.get(FUTURE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException
| CancellationException e) {
- Slog.w(TAG, "Failed to get result from transport:", e);
+ Slog.e(TAG, "Failed to get result from transport:", e);
return null;
}
}
private boolean bindService() {
- mForensicBackupTransportConfig = mContext.getString(
- com.android.internal.R.string.config_forensicBackupTransport);
- if (TextUtils.isEmpty(mForensicBackupTransportConfig)) {
+ mForensicEventTransportConfig = mContext.getString(
+ com.android.internal.R.string.config_forensicEventTransport);
+ if (TextUtils.isEmpty(mForensicEventTransportConfig)) {
+ Slog.e(TAG, "config_forensicEventTransport is empty");
return false;
}
ComponentName serviceComponent =
- ComponentName.unflattenFromString(mForensicBackupTransportConfig);
+ ComponentName.unflattenFromString(mForensicEventTransportConfig);
if (serviceComponent == null) {
+ Slog.e(TAG, "Can't get serviceComponent name");
+ return false;
+ }
+
+ try {
+ ServiceInfo serviceInfo = mContext.getPackageManager().getServiceInfo(serviceComponent,
+ 0 /* flags */);
+ if (!BIND_FORENSIC_EVENT_TRANSPORT_SERVICE.equals(serviceInfo.permission)) {
+ Slog.e(TAG, serviceComponent.flattenToShortString()
+ + " is not declared with the permission "
+ + "\"" + BIND_FORENSIC_EVENT_TRANSPORT_SERVICE + "\"");
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Unable to find serviceComponent");
return false;
}
@@ -143,7 +163,7 @@ public class BackupTransportConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- mService = IBackupTransport.Stub.asInterface(service);
+ mService = IForensicEventTransport.Stub.asInterface(service);
}
@Override
diff --git a/services/core/java/com/android/server/security/forensic/ForensicService.java b/services/core/java/com/android/server/security/forensic/ForensicService.java
index 01f630b60ff5..2be068fa2f83 100644
--- a/services/core/java/com/android/server/security/forensic/ForensicService.java
+++ b/services/core/java/com/android/server/security/forensic/ForensicService.java
@@ -16,11 +16,16 @@
package com.android.server.security.forensic;
+import static android.Manifest.permission.MANAGE_FORENSIC_STATE;
+import static android.Manifest.permission.READ_FORENSIC_STATE;
+
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.PermissionEnforcer;
import android.os.RemoteException;
import android.security.forensic.ForensicEvent;
import android.security.forensic.IForensicService;
@@ -41,16 +46,15 @@ import java.util.List;
public class ForensicService extends SystemService {
private static final String TAG = "ForensicService";
- private static final int MSG_MONITOR_STATE = 0;
- private static final int MSG_MAKE_VISIBLE = 1;
- private static final int MSG_MAKE_INVISIBLE = 2;
- private static final int MSG_ENABLE = 3;
- private static final int MSG_DISABLE = 4;
- private static final int MSG_BACKUP = 5;
+ private static final int MAX_STATE_CALLBACK_NUM = 16;
+ private static final int MSG_ADD_STATE_CALLBACK = 0;
+ private static final int MSG_REMOVE_STATE_CALLBACK = 1;
+ private static final int MSG_ENABLE = 2;
+ private static final int MSG_DISABLE = 3;
+ private static final int MSG_TRANSPORT = 4;
private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN;
- private static final int STATE_INVISIBLE = IForensicServiceStateCallback.State.INVISIBLE;
- private static final int STATE_VISIBLE = IForensicServiceStateCallback.State.VISIBLE;
+ private static final int STATE_DISABLED = IForensicServiceStateCallback.State.DISABLED;
private static final int STATE_ENABLED = IForensicServiceStateCallback.State.ENABLED;
private static final int ERROR_UNKNOWN = IForensicServiceCommandCallback.ErrorCode.UNKNOWN;
@@ -58,19 +62,19 @@ public class ForensicService extends SystemService {
IForensicServiceCommandCallback.ErrorCode.PERMISSION_DENIED;
private static final int ERROR_INVALID_STATE_TRANSITION =
IForensicServiceCommandCallback.ErrorCode.INVALID_STATE_TRANSITION;
- private static final int ERROR_BACKUP_TRANSPORT_UNAVAILABLE =
- IForensicServiceCommandCallback.ErrorCode.BACKUP_TRANSPORT_UNAVAILABLE;
+ private static final int ERROR_TRANSPORT_UNAVAILABLE =
+ IForensicServiceCommandCallback.ErrorCode.TRANSPORT_UNAVAILABLE;
private static final int ERROR_DATA_SOURCE_UNAVAILABLE =
IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE;
private final Context mContext;
private final Handler mHandler;
- private final BackupTransportConnection mBackupTransportConnection;
+ private final ForensicEventTransportConnection mForensicEventTransportConnection;
private final DataAggregator mDataAggregator;
private final BinderService mBinderService;
- private final ArrayList<IForensicServiceStateCallback> mStateMonitors = new ArrayList<>();
- private volatile int mState = STATE_INVISIBLE;
+ private final ArrayList<IForensicServiceStateCallback> mStateCallbacks = new ArrayList<>();
+ private volatile int mState = STATE_DISABLED;
public ForensicService(@NonNull Context context) {
this(new InjectorImpl(context));
@@ -81,9 +85,9 @@ public class ForensicService extends SystemService {
super(injector.getContext());
mContext = injector.getContext();
mHandler = new EventHandler(injector.getLooper(), this);
- mBackupTransportConnection = injector.getBackupTransportConnection();
+ mForensicEventTransportConnection = injector.getForensicEventransportConnection();
mDataAggregator = injector.getDataAggregator(this);
- mBinderService = new BinderService(this);
+ mBinderService = new BinderService(this, injector.getPermissionEnforcer());
}
@VisibleForTesting
@@ -94,32 +98,36 @@ public class ForensicService extends SystemService {
private static final class BinderService extends IForensicService.Stub {
final ForensicService mService;
- BinderService(ForensicService service) {
+ BinderService(ForensicService service, @NonNull PermissionEnforcer permissionEnforcer) {
+ super(permissionEnforcer);
mService = service;
}
@Override
- public void monitorState(IForensicServiceStateCallback callback) {
- mService.mHandler.obtainMessage(MSG_MONITOR_STATE, callback).sendToTarget();
- }
-
- @Override
- public void makeVisible(IForensicServiceCommandCallback callback) {
- mService.mHandler.obtainMessage(MSG_MAKE_VISIBLE, callback).sendToTarget();
+ @EnforcePermission(READ_FORENSIC_STATE)
+ public void addStateCallback(IForensicServiceStateCallback callback) {
+ addStateCallback_enforcePermission();
+ mService.mHandler.obtainMessage(MSG_ADD_STATE_CALLBACK, callback).sendToTarget();
}
@Override
- public void makeInvisible(IForensicServiceCommandCallback callback) {
- mService.mHandler.obtainMessage(MSG_MAKE_INVISIBLE, callback).sendToTarget();
+ @EnforcePermission(READ_FORENSIC_STATE)
+ public void removeStateCallback(IForensicServiceStateCallback callback) {
+ removeStateCallback_enforcePermission();
+ mService.mHandler.obtainMessage(MSG_REMOVE_STATE_CALLBACK, callback).sendToTarget();
}
@Override
+ @EnforcePermission(MANAGE_FORENSIC_STATE)
public void enable(IForensicServiceCommandCallback callback) {
+ enable_enforcePermission();
mService.mHandler.obtainMessage(MSG_ENABLE, callback).sendToTarget();
}
@Override
+ @EnforcePermission(MANAGE_FORENSIC_STATE)
public void disable(IForensicServiceCommandCallback callback) {
+ disable_enforcePermission();
mService.mHandler.obtainMessage(MSG_DISABLE, callback).sendToTarget();
}
}
@@ -135,24 +143,18 @@ public class ForensicService extends SystemService {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_MONITOR_STATE:
+ case MSG_ADD_STATE_CALLBACK:
try {
- mService.monitorState(
+ mService.addStateCallback(
(IForensicServiceStateCallback) msg.obj);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
}
break;
- case MSG_MAKE_VISIBLE:
+ case MSG_REMOVE_STATE_CALLBACK:
try {
- mService.makeVisible((IForensicServiceCommandCallback) msg.obj);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException", e);
- }
- break;
- case MSG_MAKE_INVISIBLE:
- try {
- mService.makeInvisible((IForensicServiceCommandCallback) msg.obj);
+ mService.removeStateCallback(
+ (IForensicServiceStateCallback) msg.obj);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
}
@@ -171,8 +173,8 @@ public class ForensicService extends SystemService {
Slog.e(TAG, "RemoteException", e);
}
break;
- case MSG_BACKUP:
- mService.backup((List<ForensicEvent>) msg.obj);
+ case MSG_TRANSPORT:
+ mService.transport((List<ForensicEvent>) msg.obj);
break;
default:
Slog.w(TAG, "Unknown message: " + msg.what);
@@ -180,103 +182,83 @@ public class ForensicService extends SystemService {
}
}
- private void monitorState(IForensicServiceStateCallback callback) throws RemoteException {
- for (int i = 0; i < mStateMonitors.size(); i++) {
- if (mStateMonitors.get(i).asBinder() == callback.asBinder()) {
+ private void addStateCallback(IForensicServiceStateCallback callback) throws RemoteException {
+ for (int i = 0; i < mStateCallbacks.size(); i++) {
+ if (mStateCallbacks.get(i).asBinder() == callback.asBinder()) {
return;
}
}
- mStateMonitors.add(callback);
+ mStateCallbacks.add(callback);
callback.onStateChange(mState);
}
- private void notifyStateMonitors() throws RemoteException {
- for (int i = 0; i < mStateMonitors.size(); i++) {
- mStateMonitors.get(i).onStateChange(mState);
+ private void removeStateCallback(IForensicServiceStateCallback callback)
+ throws RemoteException {
+ for (int i = 0; i < mStateCallbacks.size(); i++) {
+ if (mStateCallbacks.get(i).asBinder() == callback.asBinder()) {
+ mStateCallbacks.remove(i);
+ return;
+ }
}
}
- private void makeVisible(IForensicServiceCommandCallback callback) throws RemoteException {
- switch (mState) {
- case STATE_INVISIBLE:
- if (!mDataAggregator.initialize()) {
- callback.onFailure(ERROR_DATA_SOURCE_UNAVAILABLE);
- break;
- }
- mState = STATE_VISIBLE;
- notifyStateMonitors();
- callback.onSuccess();
- break;
- case STATE_VISIBLE:
- callback.onSuccess();
- break;
- default:
- callback.onFailure(ERROR_INVALID_STATE_TRANSITION);
+ private void notifyStateMonitors() {
+ if (mStateCallbacks.size() >= MAX_STATE_CALLBACK_NUM) {
+ mStateCallbacks.removeFirst();
}
- }
- private void makeInvisible(IForensicServiceCommandCallback callback) throws RemoteException {
- switch (mState) {
- case STATE_VISIBLE:
- case STATE_ENABLED:
- mState = STATE_INVISIBLE;
- notifyStateMonitors();
- callback.onSuccess();
- break;
- case STATE_INVISIBLE:
- callback.onSuccess();
- break;
- default:
- callback.onFailure(ERROR_INVALID_STATE_TRANSITION);
+ for (int i = 0; i < mStateCallbacks.size(); i++) {
+ try {
+ mStateCallbacks.get(i).onStateChange(mState);
+ } catch (RemoteException e) {
+ mStateCallbacks.remove(i);
+ }
}
}
private void enable(IForensicServiceCommandCallback callback) throws RemoteException {
- switch (mState) {
- case STATE_VISIBLE:
- if (!mBackupTransportConnection.initialize()) {
- callback.onFailure(ERROR_BACKUP_TRANSPORT_UNAVAILABLE);
- break;
- }
- mDataAggregator.enable();
- mState = STATE_ENABLED;
- notifyStateMonitors();
- callback.onSuccess();
- break;
- case STATE_ENABLED:
- callback.onSuccess();
- break;
- default:
- callback.onFailure(ERROR_INVALID_STATE_TRANSITION);
+ if (mState == STATE_ENABLED) {
+ callback.onSuccess();
+ return;
}
+
+ // TODO: temporarily disable the following for the CTS ForensicManagerTest.
+ // Enable it when the transport component is ready.
+ // if (!mForensicEventTransportConnection.initialize()) {
+ // callback.onFailure(ERROR_TRANSPORT_UNAVAILABLE);
+ // return;
+ // }
+
+ mDataAggregator.enable();
+ mState = STATE_ENABLED;
+ notifyStateMonitors();
+ callback.onSuccess();
}
private void disable(IForensicServiceCommandCallback callback) throws RemoteException {
- switch (mState) {
- case STATE_ENABLED:
- mBackupTransportConnection.release();
- mDataAggregator.disable();
- mState = STATE_VISIBLE;
- notifyStateMonitors();
- callback.onSuccess();
- break;
- case STATE_VISIBLE:
- callback.onSuccess();
- break;
- default:
- callback.onFailure(ERROR_INVALID_STATE_TRANSITION);
+ if (mState == STATE_DISABLED) {
+ callback.onSuccess();
+ return;
}
+
+ // TODO: temporarily disable the following for the CTS ForensicManagerTest.
+ // Enable it when the transport component is ready.
+ // mForensicEventTransportConnection.release();
+ mDataAggregator.disable();
+ mState = STATE_DISABLED;
+ notifyStateMonitors();
+ callback.onSuccess();
}
/**
* Add a list of ForensicEvent.
*/
public void addNewData(List<ForensicEvent> events) {
- mHandler.obtainMessage(MSG_BACKUP, events).sendToTarget();
+ mHandler.obtainMessage(MSG_TRANSPORT, events).sendToTarget();
}
- private void backup(List<ForensicEvent> events) {
- mBackupTransportConnection.addData(events);
+ private void transport(List<ForensicEvent> events) {
+ mForensicEventTransportConnection.addData(events);
}
@Override
@@ -296,9 +278,11 @@ public class ForensicService extends SystemService {
interface Injector {
Context getContext();
+ PermissionEnforcer getPermissionEnforcer();
+
Looper getLooper();
- BackupTransportConnection getBackupTransportConnection();
+ ForensicEventTransportConnection getForensicEventransportConnection();
DataAggregator getDataAggregator(ForensicService forensicService);
}
@@ -315,6 +299,10 @@ public class ForensicService extends SystemService {
return mContext;
}
+ @Override
+ public PermissionEnforcer getPermissionEnforcer() {
+ return PermissionEnforcer.fromContext(mContext);
+ }
@Override
public Looper getLooper() {
@@ -326,8 +314,8 @@ public class ForensicService extends SystemService {
}
@Override
- public BackupTransportConnection getBackupTransportConnection() {
- return new BackupTransportConnection(mContext);
+ public ForensicEventTransportConnection getForensicEventransportConnection() {
+ return new ForensicEventTransportConnection(mContext);
}
@Override
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6707a27d83c8..f50417d6659e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2856,7 +2856,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
void prepareForShutdown() {
for (int i = 0; i < getChildCount(); i++) {
- createSleepToken("shutdown", getChildAt(i).mDisplayId);
+ final int displayId = getChildAt(i).mDisplayId;
+ mWindowManager.mSnapshotController.mTaskSnapshotController
+ .snapshotForShutdown(displayId);
+ createSleepToken("shutdown", displayId);
}
}
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 1c8c245f7640..bd8e8f4008de 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -64,6 +64,7 @@ class SnapshotPersistQueue {
private boolean mStarted;
private final Object mLock = new Object();
private final UserManagerInternal mUserManagerInternal;
+ private boolean mShutdown;
SnapshotPersistQueue() {
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -101,6 +102,16 @@ class SnapshotPersistQueue {
}
}
+ /**
+ * Write out everything in the queue because of shutdown.
+ */
+ void shutdown() {
+ synchronized (mLock) {
+ mShutdown = true;
+ mLock.notifyAll();
+ }
+ }
+
@VisibleForTesting
void waitForQueueEmpty() {
while (true) {
@@ -193,7 +204,9 @@ class SnapshotPersistQueue {
if (isReadyToWrite) {
next.write();
}
- SystemClock.sleep(DELAY_MS);
+ if (!mShutdown) {
+ SystemClock.sleep(DELAY_MS);
+ }
}
synchronized (mLock) {
final boolean writeQueueEmpty = mWriteQueue.isEmpty();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 1f82cdb70b91..9fe3f7563902 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -307,6 +307,28 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot
}
/**
+ * Record task snapshots before shutdown.
+ */
+ void snapshotForShutdown(int displayId) {
+ if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
+ return;
+ }
+ final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
+ if (displayContent == null) {
+ return;
+ }
+ displayContent.forAllLeafTasks(task -> {
+ if (task.isVisible() && !task.isActivityTypeHome()) {
+ final TaskSnapshot snapshot = captureSnapshot(task);
+ if (snapshot != null) {
+ mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+ }
+ }
+ }, true /* traverseTopToBottom */);
+ mPersister.mSnapshotPersistQueue.shutdown();
+ }
+
+ /**
* Called when screen is being turned off.
*/
void screenTurningOff(int displayId, ScreenOffListener listener) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 54b257cff11d..8268cae12e3d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1022,12 +1022,12 @@ public class WindowManagerService extends IWindowManager.Stub
return;
}
- final boolean disableSecureWindows;
+ boolean disableSecureWindows;
try {
disableSecureWindows = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.DISABLE_SECURE_WINDOWS, 0) != 0;
} catch (Settings.SettingNotFoundException e) {
- return;
+ disableSecureWindows = false;
}
if (mDisableSecureWindows == disableSecureWindows) {
return;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index ead12826c263..091896590b6b 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -788,7 +788,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
deferResume = false;
// Already calls ensureActivityConfig
mService.mRootWindowContainer.ensureActivitiesVisible();
- mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
+ if (!mService.mRootWindowContainer.resumeFocusedTasksTopActivities()) {
+ mService.mTaskSupervisor.updateTopResumedActivityIfNeeded("endWCT-effects");
+ }
} else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
haveConfigChanges.valueAt(i).forAllActivities(r -> {
@@ -886,7 +888,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (windowingMode > -1) {
if (mService.isInLockTaskMode()
- && WindowConfiguration.inMultiWindowMode(windowingMode)) {
+ && WindowConfiguration.inMultiWindowMode(windowingMode)
+ && !container.isEmbedded()) {
Slog.w(TAG, "Dropping unsupported request to set multi-window windowing mode"
+ " during locked task mode.");
return effects;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index b982098fefa4..76d16e19e774 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -371,6 +371,9 @@ class ActiveAdmin {
}
ActiveAdmin(int userId, boolean permissionBased) {
+ if (Flags.activeAdminCleanup()) {
+ throw new UnsupportedOperationException("permission based admin no longer supported");
+ }
if (permissionBased == false) {
throw new IllegalArgumentException("Can only pass true for permissionBased admin");
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index 395ea9176877..c937e10a28ce 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.FileUtils;
import android.os.PersistableBundle;
@@ -124,17 +125,18 @@ class DevicePolicyData {
final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
- // Some DevicePolicyManager APIs can be called by (1) a DPC or (2) an app with permissions that
- // isn't a DPC. For the latter, the caller won't have to provide a ComponentName and won't be
- // mapped to an ActiveAdmin. This permission-based admin should be used to persist policies
- // set by the permission-based caller. This admin should not be added to mAdminMap or mAdminList
- // since a lot of methods in DPMS assume the ActiveAdmins here have a valid ComponentName.
- // Instead, use variants of DPMS active admin getters to include the permission-based admin.
+ /**
+ * @deprecated Do not use. Policies set by permission holders must go into DevicePolicyEngine.
+ */
+ @Deprecated
ActiveAdmin mPermissionBasedAdmin;
// Create or get the permission-based admin. The permission-based admin will not have a
// DeviceAdminInfo or ComponentName.
ActiveAdmin createOrGetPermissionBasedAdmin(int userId) {
+ if (Flags.activeAdminCleanup()) {
+ throw new UnsupportedOperationException("permission based admin no longer supported");
+ }
if (mPermissionBasedAdmin == null) {
mPermissionBasedAdmin = new ActiveAdmin(userId, /* permissionBased= */ true);
}
@@ -147,7 +149,7 @@ class DevicePolicyData {
// This is the list of component allowed to start lock task mode.
List<String> mLockTaskPackages = new ArrayList<>();
- /** @deprecated moved to {@link ActiveAdmin#protectedPackages}. */
+ /** @deprecated moved to DevicePolicyEngine. */
@Deprecated
@Nullable
List<String> mUserControlDisabledPackages;
@@ -280,7 +282,7 @@ class DevicePolicyData {
}
}
- if (policyData.mPermissionBasedAdmin != null) {
+ if (!Flags.activeAdminCleanup() && policyData.mPermissionBasedAdmin != null) {
out.startTag(null, "permission-based-admin");
policyData.mPermissionBasedAdmin.writeToXml(out);
out.endTag(null, "permission-based-admin");
@@ -521,7 +523,8 @@ class DevicePolicyData {
} catch (RuntimeException e) {
Slogf.w(TAG, e, "Failed loading admin %s", name);
}
- } else if ("permission-based-admin".equals(tag)) {
+ } else if (!Flags.activeAdminCleanup() && "permission-based-admin".equals(tag)) {
+
ActiveAdmin ap = new ActiveAdmin(policy.mUserId, /* permissionBased= */ true);
ap.readFromXml(parser, /* overwritePolicies= */ false);
policy.mPermissionBasedAdmin = ap;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index dff34389eb89..6292cbfad00b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -376,6 +376,7 @@ import android.app.backup.IBackupManager;
import android.app.compat.CompatChanges;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
+import android.app.supervision.SupervisionManagerInternal;
import android.app.trust.TrustManager;
import android.app.usage.UsageStatsManagerInternal;
import android.compat.annotation.ChangeId;
@@ -504,6 +505,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.LocalePicker;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.net.NetworkUtilsInternal;
import com.android.internal.notification.SystemNotificationChannels;
@@ -715,24 +717,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
SECURE_SETTINGS_DEVICEOWNER_ALLOWLIST.add(Settings.Secure.LOCATION_MODE);
GLOBAL_SETTINGS_ALLOWLIST = new ArraySet<>();
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.ADB_ENABLED);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.ADB_WIFI_ENABLED);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.AUTO_TIME);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.AUTO_TIME_ZONE);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.DATA_ROAMING);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.USB_MASS_STORAGE_ENABLED);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.WIFI_SLEEP_POLICY);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.STAY_ON_WHILE_PLUGGED_IN);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.PRIVATE_DNS_MODE);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.PRIVATE_DNS_SPECIFIER);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.ADB_ENABLED);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.ADB_WIFI_ENABLED);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.AUTO_TIME);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.AUTO_TIME_ZONE);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.DATA_ROAMING);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.USB_MASS_STORAGE_ENABLED);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.WIFI_SLEEP_POLICY);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.STAY_ON_WHILE_PLUGGED_IN);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.PRIVATE_DNS_MODE);
+ GLOBAL_SETTINGS_ALLOWLIST.add(PRIVATE_DNS_SPECIFIER);
GLOBAL_SETTINGS_DEPRECATED = new ArraySet<>();
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.BLUETOOTH_ON);
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED);
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.MODE_RINGER);
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.NETWORK_PREFERENCE);
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.WIFI_ON);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.BLUETOOTH_ON);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.DEVELOPMENT_SETTINGS_ENABLED);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.MODE_RINGER);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.NETWORK_PREFERENCE);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.WIFI_ON);
SYSTEM_SETTINGS_ALLOWLIST = new ArraySet<>();
SYSTEM_SETTINGS_ALLOWLIST.add(Settings.System.SCREEN_BRIGHTNESS);
@@ -775,7 +777,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Strings logged with {@link
- * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB},
+ * MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB},
* {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB},
* {@link DevicePolicyEnums#SET_NETWORK_LOGGING_ENABLED} and
* {@link DevicePolicyEnums#RETRIEVE_NETWORK_LOGS}.
@@ -786,11 +788,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* For admin apps targeting R+, throw when the app sets password requirement
* that is not taken into account at given quality. For example when quality is set
- * to {@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't
+ * to {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't
* make sense to require certain password length. If the intent is to require a password of
* certain length having at least NUMERIC quality, the admin should first call
- * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} and only then call
- * {@link android.app.admin.DevicePolicyManager#setPasswordMinimumLength}.
+ * {@link DevicePolicyManager#setPasswordQuality} and only then call
+ * {@link DevicePolicyManager#setPasswordMinimumLength}.
*
* <p>Conversely when an admin app targeting R+ lowers password quality, those
* requirements that stop making sense are reset to default values.
@@ -801,9 +803,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Admin apps targeting Android R+ may not use
- * {@link android.app.admin.DevicePolicyManager#setSecureSetting} to change the deprecated
- * {@link android.provider.Settings.Secure#LOCATION_MODE} setting. Instead they should use
- * {@link android.app.admin.DevicePolicyManager#setLocationEnabled}.
+ * {@link DevicePolicyManager#setSecureSetting} to change the deprecated
+ * {@link Settings.Secure#LOCATION_MODE} setting. Instead they should use
+ * {@link DevicePolicyManager#setLocationEnabled}.
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
@@ -849,7 +851,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private @interface CopyAccountStatus {}
/**
- * Mapping of {@link android.app.admin.DevicePolicyManager.ApplicationExemptionConstants} to
+ * Mapping of {@link DevicePolicyManager.ApplicationExemptionConstants} to
* corresponding app-ops.
*/
private static final Map<Integer, String> APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS =
@@ -881,11 +883,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Admin apps targeting Android S+ may not use
- * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} to set password quality
+ * {@link DevicePolicyManager#setPasswordQuality} to set password quality
* on the {@code DevicePolicyManager} instance obtained by calling
- * {@link android.app.admin.DevicePolicyManager#getParentProfileInstance}.
+ * {@link DevicePolicyManager#getParentProfileInstance}.
* Instead, they should use
- * {@link android.app.admin.DevicePolicyManager#setRequiredPasswordComplexity} to set
+ * {@link DevicePolicyManager#setRequiredPasswordComplexity} to set
* coarse-grained password requirements device-wide.
*/
@ChangeId
@@ -894,7 +896,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* For Admin Apps targeting U+
- * If {@link android.security.IKeyChainService#setGrant} is called with an alias with no
+ * If {@link IKeyChainService#setGrant} is called with an alias with no
* existing key, throw IllegalArgumentException.
*/
@ChangeId
@@ -926,6 +928,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final UsageStatsManagerInternal mUsageStatsManagerInternal;
final TelephonyManager mTelephonyManager;
final RoleManager mRoleManager;
+ final SupervisionManagerInternal mSupervisionManagerInternal;
+
private final LockPatternUtils mLockPatternUtils;
private final LockSettingsInternal mLockSettingsInternal;
private final DeviceAdminServiceController mDeviceAdminServiceController;
@@ -1474,8 +1478,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (packageName == null || packageName.equals(adminPackage)) {
if (mIPackageManager.getPackageInfo(adminPackage, 0, userHandle) == null
|| mIPackageManager.getReceiverInfo(aa.info.getComponent(),
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE,
userHandle) == null) {
Slogf.e(LOG_TAG, String.format(
"Admin package %s not found for user %d, removing active admin",
@@ -1693,7 +1697,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
}
- Context createContextAsUser(UserHandle user) throws PackageManager.NameNotFoundException {
+ Context createContextAsUser(UserHandle user) throws NameNotFoundException {
final String packageName = mContext.getPackageName();
return mContext.createPackageContextAsUser(packageName, 0, user);
}
@@ -2005,25 +2009,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
void settingsGlobalPutStringForUser(String name, String value, int userHandle) {
- Settings.Global.putStringForUser(mContext.getContentResolver(),
+ Global.putStringForUser(mContext.getContentResolver(),
name, value, userHandle);
}
int settingsGlobalGetInt(String name, int def) {
- return Settings.Global.getInt(mContext.getContentResolver(), name, def);
+ return Global.getInt(mContext.getContentResolver(), name, def);
}
@Nullable
String settingsGlobalGetString(String name) {
- return Settings.Global.getString(mContext.getContentResolver(), name);
+ return Global.getString(mContext.getContentResolver(), name);
}
void settingsGlobalPutInt(String name, int value) {
- Settings.Global.putInt(mContext.getContentResolver(), name, value);
+ Global.putInt(mContext.getContentResolver(), name, value);
}
void settingsGlobalPutString(String name, String value) {
- Settings.Global.putString(mContext.getContentResolver(), name, value);
+ Global.putString(mContext.getContentResolver(), name, value);
}
void settingsSystemPutStringForUser(String name, String value, int userId) {
@@ -2082,6 +2086,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
boolean isAdminInstalledCaCertAutoApproved() {
return false;
}
+
+ @Nullable
+ SupervisionManagerInternal getSupervisionManager() {
+ return LocalServices.getService(SupervisionManagerInternal.class);
+ }
}
/**
@@ -2113,6 +2122,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mIPermissionManager = Objects.requireNonNull(injector.getIPermissionManager());
mTelephonyManager = Objects.requireNonNull(injector.getTelephonyManager());
mRoleManager = Objects.requireNonNull(injector.getRoleManager());
+ if (Flags.secondaryLockscreenApiEnabled()) {
+ mSupervisionManagerInternal = injector.getSupervisionManager();
+ } else {
+ mSupervisionManagerInternal = null;
+ }
mLocalService = new LocalService();
mLockPatternUtils = injector.newLockPatternUtils();
@@ -2234,7 +2248,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return Collections.unmodifiableSet(packageNames);
}
-
private @Nullable String getDefaultRoleHolderPackageName(int resId) {
String packageNameAndSignature = mContext.getString(resId);
@@ -3194,8 +3207,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mIPackageManager.getReceiverInfo(adminName,
GET_META_DATA
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
+ | MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE, userHandle);
} catch (RemoteException e) {
// shouldn't happen.
Slogf.wtf(LOG_TAG, "Error getting receiver info", e);
@@ -3206,9 +3219,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
throw new IllegalArgumentException("Unknown admin: " + adminName);
}
- if (!permission.BIND_DEVICE_ADMIN.equals(ai.permission)) {
+ if (!BIND_DEVICE_ADMIN.equals(ai.permission)) {
final String message = "DeviceAdminReceiver " + adminName + " must be protected with "
- + permission.BIND_DEVICE_ADMIN;
+ + BIND_DEVICE_ADMIN;
Slogf.w(LOG_TAG, message);
if (throwForMissingPermission &&
ai.applicationInfo.targetSdkVersion > Build.VERSION_CODES.M) {
@@ -3966,7 +3979,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int N = admins.size();
for (int i = 0; i < N; i++) {
ActiveAdmin admin = admins.get(i);
- if ((admin.isPermissionBased || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD))
+ if (((!Flags.activeAdminCleanup() && admin.isPermissionBased)
+ || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD))
&& admin.passwordExpirationTimeout > 0L
&& now >= admin.passwordExpirationDate - EXPIRATION_GRACE_PERIOD_MS
&& admin.passwordExpirationDate > 0L) {
@@ -4399,8 +4413,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final ApplicationInfo ai;
try {
ai = mInjector.getIPackageManager().getApplicationInfo(packageName,
- (PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE), userHandle);
+ (MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE), userHandle);
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
@@ -5563,13 +5577,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
caller.getUserId());
Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
- ActiveAdmin activeAdmin = admin.getActiveAdmin();
+ final ActiveAdmin activeAdmin;
+ if (Flags.activeAdminCleanup()) {
+ if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
+ synchronized (getLockObject()) {
+ activeAdmin = getActiveAdminUncheckedLocked(
+ admin.getComponentName(), admin.getUserId());
+ }
+ } else {
+ activeAdmin = null;
+ }
+ } else {
+ activeAdmin = admin.getActiveAdmin();
+ }
// We require the caller to explicitly clear any password quality requirements set
// on the parent DPM instance, to avoid the case where password requirements are
// specified in the form of quality on the parent but complexity on the profile
// itself.
- if (!calledOnParent) {
+ if (activeAdmin != null && !calledOnParent) {
final boolean hasQualityRequirementsOnParent = activeAdmin.hasParentActiveAdmin()
&& activeAdmin.getParentActiveAdmin().mPasswordPolicy.quality
!= PASSWORD_QUALITY_UNSPECIFIED;
@@ -5593,20 +5619,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
mInjector.binderWithCleanCallingIdentity(() -> {
- // Reset the password policy.
- if (calledOnParent) {
- activeAdmin.getParentActiveAdmin().mPasswordPolicy = new PasswordPolicy();
- } else {
- activeAdmin.mPasswordPolicy = new PasswordPolicy();
+ if (activeAdmin != null) {
+ // Reset the password policy.
+ if (calledOnParent) {
+ activeAdmin.getParentActiveAdmin().mPasswordPolicy = new PasswordPolicy();
+ } else {
+ activeAdmin.mPasswordPolicy = new PasswordPolicy();
+ }
+ updatePasswordQualityCacheForUserGroup(caller.getUserId());
}
+
synchronized (getLockObject()) {
updatePasswordValidityCheckpointLocked(caller.getUserId(), calledOnParent);
}
- updatePasswordQualityCacheForUserGroup(caller.getUserId());
saveSettingsLocked(caller.getUserId());
});
-
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_COMPLEXITY)
.setAdmin(caller.getPackageName())
@@ -5965,7 +5993,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(admin != null,
"Unauthorized caller cannot call resetPassword.");
if (getTargetSdk(admin.info.getPackageName(),
- userHandle) <= android.os.Build.VERSION_CODES.M) {
+ userHandle) <= Build.VERSION_CODES.M) {
Slogf.e(LOG_TAG, "Device admin can no longer call resetPassword()");
return false;
}
@@ -6115,7 +6143,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (policy.mLastMaximumTimeToLock != Long.MAX_VALUE) {
// Make sure KEEP_SCREEN_ON is disabled, since that
// would allow bypassing of the maximum time to lock.
- mInjector.settingsGlobalPutInt(Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+ mInjector.settingsGlobalPutInt(Global.STAY_ON_WHILE_PLUGGED_IN, 0);
}
getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(parentId, timeMs);
});
@@ -6287,28 +6315,33 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int callingUserId = caller.getUserId();
ComponentName adminComponent = null;
synchronized (getLockObject()) {
- ActiveAdmin admin;
// Make sure the caller has any active admin with the right policy or
// the required permission.
if (Flags.lockNowCoexistence()) {
- admin = enforcePermissionsAndGetEnforcingAdmin(
+ EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
/* admin= */ null,
/* permissions= */ new String[]{MANAGE_DEVICE_POLICY_LOCK, LOCK_DEVICE},
/* deviceAdminPolicy= */ USES_POLICY_FORCE_LOCK,
caller.getPackageName(),
getAffectedUser(parent)
- ).getActiveAdmin();
+ );
+ if (Flags.activeAdminCleanup()) {
+ adminComponent = enforcingAdmin.getComponentName();
+ } else {
+ ActiveAdmin admin = enforcingAdmin.getActiveAdmin();
+ adminComponent = admin == null ? null : admin.info.getComponent();
+ }
} else {
- admin = getActiveAdminOrCheckPermissionForCallerLocked(
+ ActiveAdmin admin = getActiveAdminOrCheckPermissionForCallerLocked(
null,
- DeviceAdminInfo.USES_POLICY_FORCE_LOCK,
+ USES_POLICY_FORCE_LOCK,
parent,
LOCK_DEVICE);
+ adminComponent = admin == null ? null : admin.info.getComponent();
}
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_LOCK_NOW);
final long ident = mInjector.binderClearCallingIdentity();
try {
- adminComponent = admin == null ? null : admin.info.getComponent();
if (adminComponent != null) {
// For Profile Owners only, callers with only permission not allowed.
if ((flags & DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY) != 0) {
@@ -7443,7 +7476,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* privileged APIs.
* <p>
* This is done by checking that the calling package is authorized to perform the app operation
- * {@link android.app.AppOpsManager#OP_MANAGE_CREDENTIALS}.
+ * {@link AppOpsManager#OP_MANAGE_CREDENTIALS}.
*
* @param caller the calling identity
* @return {@code true} if the calling process is the credential management app.
@@ -7453,7 +7486,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
AppOpsManager appOpsManager = mInjector.getAppOpsManager();
if (appOpsManager == null) return false;
return appOpsManager.noteOpNoThrow(AppOpsManager.OP_MANAGE_CREDENTIALS, caller.getUid(),
- caller.getPackageName(), null, null) == AppOpsManager.MODE_ALLOWED;
+ caller.getPackageName(), null, null) == MODE_ALLOWED;
});
}
@@ -7764,7 +7797,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
public void wipeDataWithReason(String callerPackageName, int flags,
@NonNull String wipeReasonForUser, boolean calledOnParentInstance,
boolean factoryReset) {
- if (!mHasFeature && !hasCallingOrSelfPermission(permission.MASTER_CLEAR)) {
+ if (!mHasFeature && !hasCallingOrSelfPermission(MASTER_CLEAR)) {
return;
}
CallerIdentity caller = getCallerIdentity(callerPackageName);
@@ -7777,7 +7810,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
USES_POLICY_WIPE_DATA,
caller.getPackageName(),
factoryReset ? UserHandle.USER_ALL : getAffectedUser(calledOnParentInstance));
- ActiveAdmin admin = enforcingAdmin.getActiveAdmin();
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_WIPE_DATA);
@@ -7786,10 +7818,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
calledByProfileOwnerOnOrgOwnedDevice, calledOnParentInstance);
}
- int userId = admin != null ? admin.getUserHandle().getIdentifier()
- : caller.getUserId();
- Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser, admin,
- userId);
+ int userId;
+ ActiveAdmin admin = null;
+ if (Flags.activeAdminCleanup()) {
+ userId = enforcingAdmin.getUserId();
+ Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser,
+ enforcingAdmin, userId);
+ } else {
+ admin = enforcingAdmin.getActiveAdmin();
+ userId = admin != null ? admin.getUserHandle().getIdentifier()
+ : caller.getUserId();
+ Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser, admin,
+ userId);
+ }
+
if (calledByProfileOwnerOnOrgOwnedDevice) {
// When wipeData is called on the parent instance, it implies wiping the entire device.
if (calledOnParentInstance) {
@@ -7810,25 +7852,36 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final String adminName;
final ComponentName adminComp;
- if (admin != null) {
- if (admin.isPermissionBased) {
+ if (Flags.activeAdminCleanup()) {
+ adminComp = enforcingAdmin.getComponentName();
+ adminName = adminComp != null
+ ? adminComp.flattenToShortString()
+ : enforcingAdmin.getPackageName();
+ event.setAdmin(enforcingAdmin.getPackageName());
+ // Not including any HSUM handling here because the "else" branch in the "flag off"
+ // case below is unreachable under normal circumstances and for permission-based
+ // callers admin won't be null.
+ } else {
+ if (admin != null) {
+ if (admin.isPermissionBased) {
+ adminComp = null;
+ adminName = caller.getPackageName();
+ event.setAdmin(adminName);
+ } else {
+ adminComp = admin.info.getComponent();
+ adminName = adminComp.flattenToShortString();
+ event.setAdmin(adminComp);
+ }
+ } else {
adminComp = null;
- adminName = caller.getPackageName();
+ adminName = mInjector.getPackageManager().getPackagesForUid(caller.getUid())[0];
+ Slogf.i(LOG_TAG, "Logging wipeData() event admin as " + adminName);
event.setAdmin(adminName);
- } else {
- adminComp = admin.info.getComponent();
- adminName = adminComp.flattenToShortString();
- event.setAdmin(adminComp);
- }
- } else {
- adminComp = null;
- adminName = mInjector.getPackageManager().getPackagesForUid(caller.getUid())[0];
- Slogf.i(LOG_TAG, "Logging wipeData() event admin as " + adminName);
- event.setAdmin(adminName);
- if (mInjector.userManagerIsHeadlessSystemUserMode()) {
- // On headless system user mode, the call is meant to factory reset the whole
- // device, otherwise the caller could simply remove the current user.
- userId = UserHandle.USER_SYSTEM;
+ if (mInjector.userManagerIsHeadlessSystemUserMode()) {
+ // On headless system user mode, the call is meant to factory reset the whole
+ // device, otherwise the caller could simply remove the current user.
+ userId = UserHandle.USER_SYSTEM;
+ }
}
}
event.write();
@@ -8142,7 +8195,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
synchronized (getLockObject()) {
if (who == null) {
Preconditions.checkCallAuthorization(frpManagementAgentUid == caller.getUid()
- || hasCallingPermission(permission.MASTER_CLEAR)
+ || hasCallingPermission(MASTER_CLEAR)
|| hasCallingPermission(MANAGE_DEVICE_POLICY_FACTORY_RESET),
"Must be called by the FRP management agent on device");
admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
@@ -8316,7 +8369,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(userHandle);
for (int i = 0; i < admins.size(); i++) {
ActiveAdmin admin = admins.get(i);
- if (admin.isPermissionBased || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
+ if ((!Flags.activeAdminCleanup() && admin.isPermissionBased)
+ || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
affectedUserIds.add(admin.getUserHandle().getIdentifier());
long timeout = admin.passwordExpirationTimeout;
admin.passwordExpirationDate =
@@ -8410,7 +8464,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
*/
private int getUserIdToWipeForFailedPasswords(ActiveAdmin admin) {
final int userId = admin.getUserHandle().getIdentifier();
- if (admin.isPermissionBased) {
+ if (!Flags.activeAdminCleanup() && admin.isPermissionBased) {
return userId;
}
final ComponentName component = admin.info.getComponent();
@@ -8628,9 +8682,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Slogf.e(LOG_TAG, "Invalid proxy properties, ignoring: " + proxyProperties.toString());
return;
}
- mInjector.settingsGlobalPutString(Settings.Global.GLOBAL_HTTP_PROXY_HOST, data[0]);
- mInjector.settingsGlobalPutInt(Settings.Global.GLOBAL_HTTP_PROXY_PORT, proxyPort);
- mInjector.settingsGlobalPutString(Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+ mInjector.settingsGlobalPutString(Global.GLOBAL_HTTP_PROXY_HOST, data[0]);
+ mInjector.settingsGlobalPutInt(Global.GLOBAL_HTTP_PROXY_PORT, proxyPort);
+ mInjector.settingsGlobalPutString(Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
exclusionList);
}
@@ -8751,7 +8805,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
final int rawStatus = getEncryptionStatus();
- if ((rawStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER) && legacyApp) {
+ if ((rawStatus == ENCRYPTION_STATUS_ACTIVE_PER_USER) && legacyApp) {
return DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE;
}
return rawStatus;
@@ -8775,7 +8829,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
*/
private int getEncryptionStatus() {
if (mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
- return DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
+ return ENCRYPTION_STATUS_ACTIVE_PER_USER;
} else {
return DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
}
@@ -8970,7 +9024,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// Turn AUTO_TIME on in settings if it is required
if (required) {
mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME,
+ () -> mInjector.settingsGlobalPutInt(Global.AUTO_TIME,
1 /* AUTO_TIME on */));
}
DevicePolicyEventLogger
@@ -10402,7 +10456,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
policy.mDelegationMap.clear();
policy.mStatusBarDisabled = false;
policy.mSecondaryLockscreenEnabled = false;
- policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
+ policy.mUserProvisioningState = STATE_USER_UNMANAGED;
policy.mAffiliationIds.clear();
resetAffiliationCacheLocked();
policy.mLockTaskPackages.clear();
@@ -10437,7 +10491,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public int getUserProvisioningState(int userHandle) {
if (!mHasFeature) {
- return DevicePolicyManager.STATE_USER_UNMANAGED;
+ return STATE_USER_UNMANAGED;
}
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(canManageUsers(caller)
@@ -10492,7 +10546,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// ADB shell can only move directly from un-managed to finalized as part of
// directly setting profile-owner or device-owner.
if (getUserProvisioningState(userId)
- != DevicePolicyManager.STATE_USER_UNMANAGED
+ != STATE_USER_UNMANAGED
|| newState != STATE_USER_SETUP_FINALIZED) {
throw new IllegalStateException("Not allowed to change provisioning state "
+ "unless current provisioning state is unmanaged, and new state"
@@ -10530,9 +10584,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
// Valid transitions for normal use-cases.
switch (currentState) {
- case DevicePolicyManager.STATE_USER_UNMANAGED:
+ case STATE_USER_UNMANAGED:
// Can move to any state from unmanaged (except itself as an edge case)..
- if (newState != DevicePolicyManager.STATE_USER_UNMANAGED) {
+ if (newState != STATE_USER_UNMANAGED) {
return;
}
break;
@@ -10556,7 +10610,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
break;
case DevicePolicyManager.STATE_USER_PROFILE_FINALIZED:
// Should only move to an unmanaged state after removing the work profile.
- if (newState == DevicePolicyManager.STATE_USER_UNMANAGED) {
+ if (newState == STATE_USER_UNMANAGED) {
return;
}
break;
@@ -10928,7 +10982,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
UserHandle userHandle = UserHandle.of(userId);
userContext = mContext.createPackageContextAsUser(packageName, /* flags= */ 0,
userHandle);
- } catch (PackageManager.NameNotFoundException nnfe) {
+ } catch (NameNotFoundException nnfe) {
Slogf.w(LOG_TAG, nnfe, "%s is not installed for user %d", packageName, userId);
return null;
}
@@ -11148,20 +11202,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private boolean canQueryAdminPolicy(CallerIdentity caller) {
- return hasCallingOrSelfPermission(permission.QUERY_ADMIN_POLICY);
+ return hasCallingOrSelfPermission(QUERY_ADMIN_POLICY);
}
private boolean hasPermission(String permission, int pid, int uid) {
- return mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED;
+ return mContext.checkPermission(permission, pid, uid) == PERMISSION_GRANTED;
}
private boolean hasCallingPermission(String permission) {
- return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
+ return mContext.checkCallingPermission(permission) == PERMISSION_GRANTED;
}
private boolean hasCallingOrSelfPermission(String permission) {
return mContext.checkCallingOrSelfPermission(permission)
- == PackageManager.PERMISSION_GRANTED;
+ == PERMISSION_GRANTED;
}
private boolean hasPermissionForPreflight(CallerIdentity caller, String permission) {
@@ -11467,7 +11521,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private String getEncryptionStatusName(int encryptionStatus) {
switch (encryptionStatus) {
- case DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER:
+ case ENCRYPTION_STATUS_ACTIVE_PER_USER:
return "per-user";
case DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED:
return "unsupported";
@@ -12549,7 +12603,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if ((flags & DevicePolicyManager.SKIP_SETUP_WIZARD) != 0) {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.USER_SETUP_COMPLETE, 1, userHandle);
+ USER_SETUP_COMPLETE, 1, userHandle);
}
sendProvisioningCompletedBroadcast(
@@ -13965,8 +14019,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
List<ResolveInfo> activitiesToEnable = mIPackageManager
.queryIntentActivities(intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE,
parentUserId)
.getList();
@@ -14585,34 +14639,76 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ private boolean hasActiveSupervisionTestAdminLocked(@UserIdInt int userId) {
+ ensureLocked();
+ if (mConstants.USE_TEST_ADMIN_AS_SUPERVISION_COMPONENT) {
+ final DevicePolicyData policy = getUserData(userId);
+ for (ActiveAdmin admin : policy.mAdminMap.values()) {
+ if (admin != null && admin.testOnlyAdmin) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
@Override
public void setSecondaryLockscreenEnabled(ComponentName who, boolean enabled,
PersistableBundle options) {
- Objects.requireNonNull(who, "ComponentName is null");
-
- // Check can set secondary lockscreen enabled
- final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwner(caller));
- Preconditions.checkCallAuthorization(!isManagedProfile(caller.getUserId()),
- "User %d is not allowed to call setSecondaryLockscreenEnabled",
+ if (Flags.secondaryLockscreenApiEnabled()) {
+ final CallerIdentity caller = getCallerIdentity();
+ final boolean isRoleHolder = isCallerSystemSupervisionRoleHolder(caller);
+ synchronized (getLockObject()) {
+ // TODO(b/378102594): Remove access for test admins.
+ final boolean isTestAdmin = hasActiveSupervisionTestAdminLocked(caller.getUserId());
+ Preconditions.checkCallAuthorization(isRoleHolder || isTestAdmin,
+ "Caller (%d) is not the SYSTEM_SUPERVISION role holder",
caller.getUserId());
+ }
- synchronized (getLockObject()) {
- // Allow testOnly admins to bypass supervision config requirement.
- Preconditions.checkCallAuthorization(isAdminTestOnlyLocked(who, caller.getUserId())
- || isSupervisionComponentLocked(caller.getComponentName()), "Admin %s is not "
- + "the default supervision component", caller.getComponentName());
- DevicePolicyData policy = getUserData(caller.getUserId());
- policy.mSecondaryLockscreenEnabled = enabled;
- saveSettingsLocked(caller.getUserId());
+ if (mSupervisionManagerInternal != null) {
+ mSupervisionManagerInternal.setSupervisionLockscreenEnabledForUser(
+ caller.getUserId(), enabled, options);
+ } else {
+ synchronized (getLockObject()) {
+ DevicePolicyData policy = getUserData(caller.getUserId());
+ policy.mSecondaryLockscreenEnabled = enabled;
+ saveSettingsLocked(caller.getUserId());
+ }
+ }
+ } else {
+ Objects.requireNonNull(who, "ComponentName is null");
+
+ // Check can set secondary lockscreen enabled
+ final CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(!isManagedProfile(caller.getUserId()),
+ "User %d is not allowed to call setSecondaryLockscreenEnabled",
+ caller.getUserId());
+
+ synchronized (getLockObject()) {
+ // Allow testOnly admins to bypass supervision config requirement.
+ Preconditions.checkCallAuthorization(isAdminTestOnlyLocked(who, caller.getUserId())
+ || isSupervisionComponentLocked(caller.getComponentName()),
+ "Admin %s is not the default supervision component",
+ caller.getComponentName());
+ DevicePolicyData policy = getUserData(caller.getUserId());
+ policy.mSecondaryLockscreenEnabled = enabled;
+ saveSettingsLocked(caller.getUserId());
+ }
}
}
@Override
public boolean isSecondaryLockscreenEnabled(@NonNull UserHandle userHandle) {
- synchronized (getLockObject()) {
- return getUserData(userHandle.getIdentifier()).mSecondaryLockscreenEnabled;
+ if (Flags.secondaryLockscreenApiEnabled() && mSupervisionManagerInternal != null) {
+ return mSupervisionManagerInternal.isSupervisionLockscreenEnabledForUser(
+ userHandle.getIdentifier());
+ } else {
+ synchronized (getLockObject()) {
+ return getUserData(userHandle.getIdentifier()).mSecondaryLockscreenEnabled;
+ }
}
}
@@ -14811,7 +14907,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (policy == null) {
// We default on the power button menu, in order to be consistent with pre-P
// behaviour.
- return DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+ return LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
}
return policy.getFlags();
}
@@ -14940,7 +15036,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
"Permission denial: device owners cannot update %1$s", setting));
}
- if (Settings.Global.STAY_ON_WHILE_PLUGGED_IN.equals(setting)) {
+ if (Global.STAY_ON_WHILE_PLUGGED_IN.equals(setting)) {
// ignore if it contradicts an existing policy
long timeMs = getMaximumTimeToLock(
who, mInjector.userHandleGetCallingUserId(), /* parent */ false);
@@ -15445,7 +15541,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int N = users.size();
for (int i = 0; i < N; i++) {
int userHandle = users.get(i).id;
- if (mInjector.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
+ if (mInjector.settingsSecureGetIntForUser(USER_SETUP_COMPLETE, 0,
userHandle) != 0) {
DevicePolicyData policy = getUserData(userHandle);
if (!policy.mUserSetupComplete) {
@@ -15473,7 +15569,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private class SetupContentObserver extends ContentObserver {
private final Uri mUserSetupComplete = Settings.Secure.getUriFor(
- Settings.Secure.USER_SETUP_COMPLETE);
+ USER_SETUP_COMPLETE);
private final Uri mPaired = Settings.Secure.getUriFor(Settings.Secure.DEVICE_PAIRED);
private final Uri mDefaultImeChanged = Settings.Secure.getUriFor(
Settings.Secure.DEFAULT_INPUT_METHOD);
@@ -15521,7 +15617,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private class DevicePolicyConstantsObserver extends ContentObserver {
final Uri mConstantsUri =
- Settings.Global.getUriFor(Settings.Global.DEVICE_POLICY_CONSTANTS);
+ Global.getUriFor(Global.DEVICE_POLICY_CONSTANTS);
DevicePolicyConstantsObserver(Handler handler) {
super(handler);
@@ -15814,9 +15910,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int uid = Objects.requireNonNull(
mInjector.getPackageManager().getApplicationInfoAsUser(
Objects.requireNonNull(packageName), /* flags= */ 0, userId)).uid;
- return PackageManager.PERMISSION_GRANTED
+ return PERMISSION_GRANTED
== ActivityManager.checkComponentPermission(
- android.Manifest.permission.MODIFY_QUIET_MODE, uid, /* owningUid= */
+ permission.MODIFY_QUIET_MODE, uid, /* owningUid= */
-1, /* exported= */ true);
} catch (NameNotFoundException ex) {
Slogf.w(LOG_TAG, "Cannot find the package %s to check for permissions.",
@@ -15953,7 +16049,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
return getDefaultCrossProfilePackages().contains(packageName)
- ? AppOpsManager.MODE_ALLOWED
+ ? MODE_ALLOWED
: AppOpsManager.opToDefaultMode(AppOpsManager.OP_INTERACT_ACROSS_PROFILES);
}
@@ -16272,7 +16368,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (admin.mPasswordPolicy.quality < minPasswordQuality) {
return false;
}
- return admin.isPermissionBased || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ return (!Flags.activeAdminCleanup() && admin.isPermissionBased)
+ || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
}
@Override
@@ -16678,13 +16775,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
synchronized (getLockObject()) {
long ident = mInjector.binderClearCallingIdentity();
boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
- >= android.os.Build.VERSION_CODES.Q;
+ >= Build.VERSION_CODES.Q;
try {
if (!isPostQAdmin) {
// Legacy admins assume that they cannot control pre-M apps
if (getTargetSdk(packageName, caller.getUserId())
- < android.os.Build.VERSION_CODES.M) {
+ < Build.VERSION_CODES.M) {
callback.sendResult(null);
return;
}
@@ -16695,7 +16792,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
if (grantState == PERMISSION_GRANT_STATE_GRANTED
|| grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
- || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
+ || grantState == PERMISSION_GRANT_STATE_DEFAULT) {
AdminPermissionControlParams permissionParams =
new AdminPermissionControlParams(packageName, permission,
grantState,
@@ -16730,26 +16827,26 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private static final List<String> SENSOR_PERMISSIONS = new ArrayList<>();
{
- SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
- SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
- SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
- SENSOR_PERMISSIONS.add(Manifest.permission.CAMERA);
- SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_AUDIO);
- SENSOR_PERMISSIONS.add(Manifest.permission.ACTIVITY_RECOGNITION);
- SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
- SENSOR_PERMISSIONS.add(Manifest.permission.BACKGROUND_CAMERA);
- SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_BACKGROUND_AUDIO);
- SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
+ SENSOR_PERMISSIONS.add(permission.ACCESS_FINE_LOCATION);
+ SENSOR_PERMISSIONS.add(permission.ACCESS_BACKGROUND_LOCATION);
+ SENSOR_PERMISSIONS.add(permission.ACCESS_COARSE_LOCATION);
+ SENSOR_PERMISSIONS.add(permission.CAMERA);
+ SENSOR_PERMISSIONS.add(permission.RECORD_AUDIO);
+ SENSOR_PERMISSIONS.add(permission.ACTIVITY_RECOGNITION);
+ SENSOR_PERMISSIONS.add(permission.BODY_SENSORS);
+ SENSOR_PERMISSIONS.add(permission.BACKGROUND_CAMERA);
+ SENSOR_PERMISSIONS.add(permission.RECORD_BACKGROUND_AUDIO);
+ SENSOR_PERMISSIONS.add(permission.BODY_SENSORS_BACKGROUND);
}
private boolean canGrantPermission(CallerIdentity caller, String permission,
String targetPackageName) {
boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
- >= android.os.Build.VERSION_CODES.Q;
+ >= Build.VERSION_CODES.Q;
if (!isPostQAdmin) {
// Legacy admins assume that they cannot control pre-M apps
if (getTargetSdk(targetPackageName, caller.getUserId())
- < android.os.Build.VERSION_CODES.M) {
+ < Build.VERSION_CODES.M) {
return false;
}
}
@@ -16796,7 +16893,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
throws RemoteException {
int granted;
if (getTargetSdk(caller.getPackageName(), caller.getUserId())
- < android.os.Build.VERSION_CODES.Q) {
+ < Build.VERSION_CODES.Q) {
// The per-Q behavior was to not check the app-ops state.
granted = mIPackageManager.checkPermission(permission, packageName, userId);
} else {
@@ -16805,11 +16902,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (packageState == null) {
Slog.w(LOG_TAG, "Can't get permission state for missing package "
+ packageName);
- return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+ return PERMISSION_GRANT_STATE_DEFAULT;
} else if (!packageState.getUserStateOrDefault(userId).isInstalled()) {
Slog.w(LOG_TAG, "Can't get permission state for uninstalled package "
+ packageName);
- return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+ return PERMISSION_GRANT_STATE_DEFAULT;
} else {
if (PermissionChecker.checkPermissionForPreflight(mContext, permission,
PermissionChecker.PID_UNKNOWN,
@@ -16817,7 +16914,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
!= PermissionChecker.PERMISSION_GRANTED) {
granted = PackageManager.PERMISSION_DENIED;
} else {
- granted = PackageManager.PERMISSION_GRANTED;
+ granted = PERMISSION_GRANTED;
}
}
@@ -16828,11 +16925,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if ((permFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED)
!= PackageManager.FLAG_PERMISSION_POLICY_FIXED) {
// Not controlled by policy
- return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+ return PERMISSION_GRANT_STATE_DEFAULT;
} else {
// Policy controlled so return result based on permission grant state
- return granted == PackageManager.PERMISSION_GRANTED
- ? DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
+ return granted == PERMISSION_GRANTED
+ ? PERMISSION_GRANT_STATE_GRANTED
: DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
}
}
@@ -16952,9 +17049,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
if (action != null) {
switch (action) {
- case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE:
+ case ACTION_PROVISION_MANAGED_PROFILE:
return checkManagedProfileProvisioningPreCondition(packageName, userId);
- case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
+ case ACTION_PROVISION_MANAGED_DEVICE:
case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
return checkDeviceOwnerProvisioningPreCondition(componentName, userId);
}
@@ -18301,7 +18398,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
boolean isUserCompleted = mInjector.settingsSecureGetIntForUser(
- Settings.Secure.USER_SETUP_COMPLETE, 0, userId) != 0;
+ USER_SETUP_COMPLETE, 0, userId) != 0;
DevicePolicyData policy = getUserData(userId);
policy.mUserSetupComplete = isUserCompleted;
mStateCache.setDeviceProvisioned(isUserCompleted);
@@ -19148,6 +19245,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ private boolean isAnyResetPasswordTokenActiveForUser(int userId) {
+ return mDevicePolicyEngine
+ .getLocalPoliciesSetByAdmins(PolicyDefinition.RESET_PASSWORD_TOKEN, userId)
+ .values()
+ .stream()
+ .anyMatch((p) -> isResetPasswordTokenActiveForUserLocked(p.getValue(), userId));
+ }
+
private boolean isResetPasswordTokenActiveForUserLocked(
long passwordTokenHandle, int userHandle) {
if (passwordTokenHandle != 0) {
@@ -19924,7 +20029,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private boolean isDeviceAB() {
- return "true".equalsIgnoreCase(android.os.SystemProperties
+ return "true".equalsIgnoreCase(SystemProperties
.get(AB_DEVICE_KEY, ""));
}
@@ -20191,7 +20296,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mOwners.hasDeviceOwner()
&& mInjector.getIActivityManager().getLockTaskModeState()
== ActivityManager.LOCK_TASK_MODE_LOCKED
- && !isLockTaskFeatureEnabled(DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO)
+ && !isLockTaskFeatureEnabled(LOCK_TASK_FEATURE_SYSTEM_INFO)
&& !deviceHasKeyguard()
&& !inEphemeralUserSession();
}
@@ -20202,7 +20307,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
int lockTaskFeatures = policy == null
// We default on the power button menu, in order to be consistent with pre-P
// behaviour.
- ? DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+ ? LOCK_TASK_FEATURE_GLOBAL_ACTIONS
: policy.getFlags();
return (lockTaskFeatures & lockTaskFeature) == lockTaskFeature;
}
@@ -20948,7 +21053,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private boolean canHandleCheckPolicyComplianceIntent(CallerIdentity caller) {
mInjector.binderWithCleanCallingIdentity(() -> {
- final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE);
+ final Intent intent = new Intent(ACTION_CHECK_POLICY_COMPLIANCE);
intent.setPackage(caller.getPackageName());
final List<ResolveInfo> handlers =
mInjector.getPackageManager().queryIntentActivitiesAsUser(intent, /* flags= */
@@ -21003,6 +21108,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()),
String.format(NOT_SYSTEM_CALLER_MSG,
"call canProfileOwnerResetPasswordWhenLocked"));
+ if (Flags.resetPasswordWithTokenCoexistence()) {
+ return isAnyResetPasswordTokenActiveForUser(userId);
+ }
synchronized (getLockObject()) {
final ActiveAdmin poAdmin = getProfileOwnerAdminLocked(userId);
DevicePolicyData policy = getUserData(userId);
@@ -21154,6 +21262,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+ if (Flags.splitCreateManagedProfileEnabled()) {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ UserHandle managedProfileUser =
+ createManagedProfileInternal(provisioningParams, caller);
+ maybeMigrateAccount(managedProfileUser.getIdentifier(), caller.getUserId(),
+ provisioningParams.getAccountToMigrate(),
+ provisioningParams.isKeepingAccountOnMigration(), callerPackage);
+ finalizeCreateManagedProfileInternal(provisioningParams, managedProfileUser);
+ return managedProfileUser;
+ });
+ }
provisioningParams.logParams(callerPackage);
UserInfo userInfo = null;
@@ -21247,6 +21366,130 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
@Override
+ public UserHandle createManagedProfile(
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull String callerPackage) {
+ Objects.requireNonNull(provisioningParams, "provisioningParams is null");
+ Objects.requireNonNull(callerPackage, "callerPackage is null");
+ Objects.requireNonNull(provisioningParams.getProfileAdminComponentName(), "admin is null");
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+ CallerIdentity caller = getCallerIdentity(callerPackage);
+
+ return mInjector.binderWithCleanCallingIdentity(() ->
+ createManagedProfileInternal(provisioningParams, caller));
+ }
+
+ private UserHandle createManagedProfileInternal(
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull CallerIdentity caller) {
+ provisioningParams.logParams(caller.getPackageName());
+ final ComponentName admin = provisioningParams.getProfileAdminComponentName();
+ final int callingUserId = caller.getUserId();
+ UserInfo userInfo = null;
+ try {
+ final int result = checkProvisioningPreconditionSkipPermission(
+ ACTION_PROVISION_MANAGED_PROFILE, admin, callingUserId);
+ if (result != STATUS_OK) {
+ throw new ServiceSpecificException(
+ ERROR_PRE_CONDITION_FAILED,
+ "Provisioning preconditions failed with result: " + result);
+ }
+
+ final long startTime = SystemClock.elapsedRealtime();
+
+ onCreateAndProvisionManagedProfileStarted(provisioningParams);
+
+ userInfo = createProfileForUser(provisioningParams, callingUserId);
+ if (userInfo == null) {
+ throw new ServiceSpecificException(
+ ERROR_PROFILE_CREATION_FAILED,
+ "Error creating profile, createProfileForUserEvenWhenDisallowed "
+ + "returned null.");
+ }
+ resetInteractAcrossProfilesAppOps(caller.getUserId());
+ logEventDuration(
+ DevicePolicyEnums.PLATFORM_PROVISIONING_CREATE_PROFILE_MS,
+ startTime,
+ caller.getPackageName());
+
+ maybeInstallDevicePolicyManagementRoleHolderInUser(userInfo.id);
+ installExistingAdminPackage(userInfo.id, admin.getPackageName());
+
+ if (!enableAdminAndSetProfileOwner(userInfo.id, caller.getUserId(), admin)) {
+ throw new ServiceSpecificException(
+ ERROR_SETTING_PROFILE_OWNER_FAILED,
+ "Error setting profile owner.");
+ }
+ setUserSetupComplete(userInfo.id);
+ startProfileForSetup(userInfo.id, caller.getPackageName());
+
+ if (provisioningParams.isOrganizationOwnedProvisioning()) {
+ synchronized (getLockObject()) {
+ setProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(admin, userInfo.id,
+ true);
+ }
+ }
+ return userInfo.getUserHandle();
+ } catch (Exception e) {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_ERROR)
+ .setStrings(caller.getPackageName())
+ .write();
+ // In case of any errors during provisioning, remove the newly created profile.
+ if (userInfo != null) {
+ mUserManager.removeUserEvenWhenDisallowed(userInfo.id);
+ }
+ throw e;
+ }
+ }
+
+ private UserInfo createProfileForUser(ManagedProfileProvisioningParams params, int userId) {
+ final Set<String> nonRequiredApps = params.isLeaveAllSystemAppsEnabled()
+ ? Collections.emptySet()
+ : mOverlayPackagesProvider.getNonRequiredApps(params.getProfileAdminComponentName(),
+ userId, ACTION_PROVISION_MANAGED_PROFILE);
+ if (nonRequiredApps.isEmpty()) {
+ Slogf.i(LOG_TAG, "No disallowed packages for the managed profile.");
+ } else {
+ for (String packageName : nonRequiredApps) {
+ Slogf.i(LOG_TAG, "Disallowed package [" + packageName + "]");
+ }
+ }
+ return mUserManager.createProfileForUserEvenWhenDisallowed(
+ params.getProfileName(),
+ UserManager.USER_TYPE_PROFILE_MANAGED,
+ UserInfo.FLAG_DISABLED,
+ userId,
+ nonRequiredApps.toArray(new String[nonRequiredApps.size()]));
+ }
+
+ @Override
+ public void finalizeCreateManagedProfile(
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull UserHandle managedProfileUser) {
+ Objects.requireNonNull(provisioningParams, "provisioningParams is null");
+ Objects.requireNonNull(managedProfileUser, "managedProfileUser is null");
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ finalizeCreateManagedProfileInternal(provisioningParams, managedProfileUser);
+ });
+ }
+
+ private void finalizeCreateManagedProfileInternal(
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull UserHandle managedProfileUser
+ ) {
+ onCreateAndProvisionManagedProfileCompleted(provisioningParams);
+ sendProvisioningCompletedBroadcast(
+ managedProfileUser.getIdentifier(),
+ ACTION_PROVISION_MANAGED_PROFILE,
+ provisioningParams.isLeaveAllSystemAppsEnabled());
+ }
+
+ @Override
public void finalizeWorkProfileProvisioning(UserHandle managedProfileUser,
Account migratedAccount) {
Preconditions.checkCallAuthorization(
@@ -21416,7 +21659,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private void pregrantDefaultInteractAcrossProfilesAppOps(@UserIdInt int userId) {
final String op =
- AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES);
+ AppOpsManager.permissionToOp(permission.INTERACT_ACROSS_PROFILES);
for (String packageName : getConfigurableDefaultCrossProfilePackages(userId)) {
if (!appOpIsDefaultOrAllowed(userId, op, packageName)) {
continue;
@@ -21619,7 +21862,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Slogf.i(LOG_TAG, "Account removed from the primary user.");
} else {
// TODO(174768447): Revisit start activity logic.
- final Intent removeIntent = result.getParcelable(AccountManager.KEY_INTENT, android.content.Intent.class);
+ final Intent removeIntent =
+ result.getParcelable(AccountManager.KEY_INTENT, Intent.class);
removeIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
if (removeIntent != null) {
Slogf.i(LOG_TAG, "Starting activity to remove account");
@@ -21915,7 +22159,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
synchronized (getLockObject()) {
mInjector.settingsGlobalPutStringForUser(
- Settings.Global.DEVICE_DEMO_MODE, Integer.toString(/* value= */ 1), userId);
+ Global.DEVICE_DEMO_MODE, Integer.toString(/* value= */ 1), userId);
}
setUserProvisioningState(STATE_USER_SETUP_FINALIZED, userId);
@@ -22178,7 +22422,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean isDevicePotentiallyStolen(String callerPackageName) {
final CallerIdentity caller = getCallerIdentity(callerPackageName);
- if (!android.app.admin.flags.Flags.deviceTheftImplEnabled()) {
+ if (!Flags.deviceTheftImplEnabled()) {
return false;
}
enforcePermission(QUERY_DEVICE_STOLEN_STATE, caller.getPackageName(),
@@ -22214,7 +22458,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+ permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
Objects.requireNonNull(drawables, "drawables must be provided.");
@@ -22230,7 +22474,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void resetDrawables(@NonNull List<String> drawableIds) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+ permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
Objects.requireNonNull(drawableIds, "drawableIds must be provided.");
@@ -22256,7 +22500,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setStrings(@NonNull List<DevicePolicyStringResource> strings) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+ permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
Objects.requireNonNull(strings, "strings must be provided.");
@@ -22271,7 +22515,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void resetStrings(@NonNull List<String> stringIds) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+ permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
mInjector.binderWithCleanCallingIdentity(() -> {
if (mDeviceManagementResourcesProvider.removeStrings(stringIds)) {
@@ -22341,7 +22585,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState() {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_ROLE_HOLDERS));
+ permission.MANAGE_ROLE_HOLDERS));
setBypassDevicePolicyManagementRoleQualificationStateInternal(
/* currentRoleHolder= */ null, /* allowBypass= */ false);
}
@@ -22349,7 +22593,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean shouldAllowBypassingDevicePolicyManagementRoleQualification() {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_ROLE_HOLDERS));
+ permission.MANAGE_ROLE_HOLDERS));
return mInjector.binderWithCleanCallingIdentity(() -> {
if (getUserData(
UserHandle.USER_SYSTEM).mBypassDevicePolicyManagementRoleQualifications) {
@@ -23345,7 +23589,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return EnforcingAdmin.createDeviceAdminEnforcingAdmin(admin.info.getComponent(), userId,
admin);
}
- admin = getUserData(userId).createOrGetPermissionBasedAdmin(userId);
+ admin = Flags.activeAdminCleanup()
+ ? null : getUserData(userId).createOrGetPermissionBasedAdmin(userId);
return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId, admin);
}
@@ -23368,8 +23613,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
- admin = getUserData(userId).createOrGetPermissionBasedAdmin(userId);
+ admin = Flags.activeAdminCleanup()
+ ? null : getUserData(userId).createOrGetPermissionBasedAdmin(userId);
return EnforcingAdmin.createEnforcingAdmin(packageName, userId, admin);
}
@@ -23941,7 +24186,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!isRuntimePermission(permission)) {
continue;
}
- int grantState = DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+ int grantState = PERMISSION_GRANT_STATE_DEFAULT;
try {
grantState = getPermissionGrantStateForUser(
packageInfo.packageName, permission,
@@ -23954,7 +24199,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Slogf.e(LOG_TAG, e, "Error retrieving permission grant state for %s "
+ "and %s", packageInfo.packageName, permission);
}
- if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
+ if (grantState == PERMISSION_GRANT_STATE_DEFAULT) {
// Not Controlled by a policy
continue;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 58e3a7d236b4..1fd628a20afa 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -23,6 +23,7 @@ import android.app.admin.DeviceAdminAuthority;
import android.app.admin.DpcAuthority;
import android.app.admin.RoleAuthority;
import android.app.admin.UnknownAuthority;
+import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.UserHandle;
@@ -295,9 +296,17 @@ final class EnforcingAdmin {
@Nullable
public ActiveAdmin getActiveAdmin() {
+ if (Flags.activeAdminCleanup()) {
+ throw new UnsupportedOperationException("getActiveAdmin() no longer supported");
+ }
return mActiveAdmin;
}
+ @Nullable
+ ComponentName getComponentName() {
+ return mComponentName;
+ }
+
@NonNull
android.app.admin.EnforcingAdmin getParcelableAdmin() {
Authority authority;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 19b03437292f..3e7c4ef4019f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -251,6 +251,7 @@ import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
import com.android.server.security.adaptiveauthentication.AdaptiveAuthenticationService;
import com.android.server.security.advancedprotection.AdvancedProtectionService;
+import com.android.server.security.forensic.ForensicService;
import com.android.server.security.rkp.RemoteProvisioningService;
import com.android.server.selinux.SelinuxAuditLogsService;
import com.android.server.sensorprivacy.SensorPrivacyService;
@@ -1760,6 +1761,13 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(LogcatManagerService.class);
t.traceEnd();
+ if (!isWatch && !isTv && !isAutomotive
+ && android.security.Flags.aflApi()) {
+ t.traceBegin("StartForensicService");
+ mSystemServiceManager.startService(ForensicService.class);
+ t.traceEnd();
+ }
+
if (AppFunctionManagerConfiguration.isSupported(context)) {
t.traceBegin("StartAppFunctionManager");
mSystemServiceManager.startService(AppFunctionManagerService.class);
@@ -2761,8 +2769,9 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(WEAR_MODE_SERVICE_CLASS);
t.traceEnd();
- boolean enableWristOrientationService = SystemProperties.getBoolean(
- "config.enable_wristorientation", false);
+ boolean enableWristOrientationService =
+ !android.server.Flags.migrateWristOrientation()
+ && SystemProperties.getBoolean("config.enable_wristorientation", false);
if (enableWristOrientationService) {
t.traceBegin("StartWristOrientationService");
mSystemServiceManager.startService(WRIST_ORIENTATION_SERVICE_CLASS);
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
index e2ac22de29a4..4412968999e5 100644
--- a/services/java/com/android/server/flags.aconfig
+++ b/services/java/com/android/server/flags.aconfig
@@ -39,6 +39,14 @@ flag {
}
flag {
+ name: "migrate_wrist_orientation"
+ namespace: "wear_frameworks"
+ description: "Migrate wrist orientation service functionality to wear settings service"
+ bug: "352725980"
+ is_fixed_read_only: true
+}
+
+flag {
name: "allow_network_time_update_service"
namespace: "wear_systems"
description: "Allow NetworkTimeUpdateService on Wear"
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index 67e254782f6d..53a25dd454ef 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -21,10 +21,11 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.supervision.ISupervisionManager;
+import android.app.supervision.SupervisionManagerInternal;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.UserInfo;
-import android.os.Bundle;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -179,8 +180,15 @@ public class SupervisionService extends ISupervisionManager.Stub {
}
@Override
+ public boolean isSupervisionLockscreenEnabledForUser(@UserIdInt int userId) {
+ synchronized (getLockObject()) {
+ return getUserDataLocked(userId).supervisionLockScreenEnabled;
+ }
+ }
+
+ @Override
public void setSupervisionLockscreenEnabledForUser(
- @UserIdInt int userId, boolean enabled, @Nullable Bundle options) {
+ @UserIdInt int userId, boolean enabled, @Nullable PersistableBundle options) {
synchronized (getLockObject()) {
SupervisionUserData data = getUserDataLocked(userId);
data.supervisionLockScreenEnabled = enabled;
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionUserData.java b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java
index 56162372f740..1dd48f581bf4 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionUserData.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java
@@ -19,7 +19,7 @@ package com.android.server.supervision;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.os.Bundle;
+import android.os.PersistableBundle;
import android.util.IndentingPrintWriter;
/** User specific data, used internally by the {@link SupervisionService}. */
@@ -27,7 +27,7 @@ public class SupervisionUserData {
public final @UserIdInt int userId;
public boolean supervisionEnabled;
public boolean supervisionLockScreenEnabled;
- @Nullable public Bundle supervisionLockScreenOptions;
+ @Nullable public PersistableBundle supervisionLockScreenOptions;
public SupervisionUserData(@UserIdInt int userId) {
this.userId = userId;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
index 1be5cef28244..acd34e323dc2 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
@@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -60,6 +61,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.List;
@AppModeFull
@AppModeNonSdkSandbox
@@ -124,7 +126,8 @@ public class BroadcastHelperTest {
@Test
public void changeNonExportedComponent_sendPackageChangedBroadcastToSystem_withPermission()
throws Exception {
- changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */);
+ changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
+ new String[0] /* sharedPackages */);
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mMockActivityManagerInternal).broadcastIntentWithCallback(
@@ -140,7 +143,8 @@ public class BroadcastHelperTest {
@Test
public void changeNonExportedComponent_sendPackageChangedBroadcastToApplicationItself()
throws Exception {
- changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */);
+ changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
+ new String[0] /* sharedPackages */);
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
@@ -150,9 +154,45 @@ public class BroadcastHelperTest {
assertThat(intent.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
}
+ @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
+ @Test
+ public void changeNonExportedComponent_sendPackageChangedBroadcastToSharedUserIdApplications()
+ throws Exception {
+ changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
+ new String[]{"shared.package"} /* sharedPackages */);
+
+ ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+ ArgumentCaptor<String[]> captorRequiredPermissions = ArgumentCaptor.forClass(
+ String[].class);
+ verify(mMockActivityManagerInternal, times(3)).broadcastIntentWithCallback(
+ captorIntent.capture(), eq(null), captorRequiredPermissions.capture(), anyInt(),
+ eq(null), eq(null), eq(null));
+ List<Intent> intents = captorIntent.getAllValues();
+ List<String[]> requiredPermissions = captorRequiredPermissions.getAllValues();
+ assertNotNull(intents);
+ assertThat(intents.size()).isEqualTo(3);
+
+ final Intent intent1 = intents.get(0);
+ final String[] requiredPermission1 = requiredPermissions.get(0);
+ assertThat(intent1.getPackage()).isEqualTo("android");
+ assertThat(requiredPermission1).isEqualTo(
+ new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+
+ final Intent intent2 = intents.get(1);
+ final String[] requiredPermission2 = requiredPermissions.get(1);
+ assertThat(intent2.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
+ assertThat(requiredPermission2).isNull();
+
+ final Intent intent3 = intents.get(2);
+ final String[] requiredPermission3 = requiredPermissions.get(2);
+ assertThat(intent3.getPackage()).isEqualTo("shared.package");
+ assertThat(requiredPermission3).isNull();
+ }
+
@Test
public void changeExportedComponent_sendPackageChangedBroadcastToAll() throws Exception {
- changeComponentAndSendPackageChangedBroadcast(true /* changeExportedComponent */);
+ changeComponentAndSendPackageChangedBroadcast(true /* changeExportedComponent */,
+ new String[0] /* sharedPackages */);
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
@@ -162,11 +202,14 @@ public class BroadcastHelperTest {
assertNull(intent.getPackage());
}
- private void changeComponentAndSendPackageChangedBroadcast(boolean changeExportedComponent) {
+ private void changeComponentAndSendPackageChangedBroadcast(boolean changeExportedComponent,
+ String[] sharedPackages) {
when(mMockSnapshot.getPackageStateInternal(eq(PACKAGE_CHANGED_TEST_PACKAGE_NAME),
anyInt())).thenReturn(mMockPackageStateInternal);
when(mMockSnapshot.isInstantAppInternal(any(), anyInt(), anyInt())).thenReturn(false);
when(mMockSnapshot.getVisibilityAllowLists(any(), any())).thenReturn(null);
+ when(mMockSnapshot.getSharedUserPackagesForPackage(eq(PACKAGE_CHANGED_TEST_PACKAGE_NAME),
+ anyInt())).thenReturn(sharedPackages);
when(mMockPackageStateInternal.getPkg()).thenReturn(mMockAndroidPackageInternal);
when(mMockParsedActivity.getClassName()).thenReturn(
diff --git a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
index 08155c7b3f98..9772ef929eae 100644
--- a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
+++ b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
@@ -2380,10 +2380,14 @@ public class VpnTest extends VpnTestBase {
@Test
public void doTestMigrateIkeSession_Vcn() throws Exception {
final int expectedKeepalive = 2097; // Any unlikely number will do
- final NetworkCapabilities vcnNc = new NetworkCapabilities.Builder()
- .addTransportType(TRANSPORT_CELLULAR)
- .setTransportInfo(new VcnTransportInfo(TEST_SUB_ID, expectedKeepalive))
- .build();
+ final NetworkCapabilities vcnNc =
+ new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .setTransportInfo(
+ new VcnTransportInfo.Builder()
+ .setMinUdpPort4500NatTimeoutSeconds(expectedKeepalive)
+ .build())
+ .build();
final Ikev2VpnProfile ikev2VpnProfile = makeIkeV2VpnProfile(
true /* isAutomaticIpVersionSelectionEnabled */,
true /* isAutomaticNattKeepaliveTimerEnabled */,
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 30de0e8c7981..8dc8c14f8948 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -67,7 +67,6 @@ import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS;
-import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.TEMPORARY_QUOTA_CHANGED;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA;
@@ -152,7 +151,6 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.flag.util.FlagSetException;
@@ -436,8 +434,7 @@ public final class AlarmManagerServiceTest {
*/
private void disableFlagsNotSetByAnnotation() {
try {
- mSetFlagsRule.disableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS,
- Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS);
} catch (FlagSetException fse) {
// Expected if the test about to be run requires this enabled.
}
@@ -523,13 +520,11 @@ public final class AlarmManagerServiceTest {
mService.onStart();
- if (Flags.useFrozenStateToDropListenerAlarms()) {
- final ArgumentCaptor<ActivityManager.UidFrozenStateChangedCallback> frozenCaptor =
- ArgumentCaptor.forClass(ActivityManager.UidFrozenStateChangedCallback.class);
- verify(mActivityManager).registerUidFrozenStateChangedCallback(
- any(HandlerExecutor.class), frozenCaptor.capture());
- mUidFrozenStateCallback = frozenCaptor.getValue();
- }
+ final ArgumentCaptor<ActivityManager.UidFrozenStateChangedCallback> frozenCaptor =
+ ArgumentCaptor.forClass(ActivityManager.UidFrozenStateChangedCallback.class);
+ verify(mActivityManager).registerUidFrozenStateChangedCallback(
+ any(HandlerExecutor.class), frozenCaptor.capture());
+ mUidFrozenStateCallback = frozenCaptor.getValue();
// Unable to mock mMockContext to return a mock stats manager.
// So just mocking the whole MetricsHelper instance.
@@ -3744,79 +3739,11 @@ public final class AlarmManagerServiceTest {
testTemporaryQuota_bumpedBeforeDeferral(STANDBY_BUCKET_RARE);
}
- @Test
- public void exactListenerAlarmsRemovedOnCached() {
- mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);
-
- setTestAlarmWithListener(ELAPSED_REALTIME, 31, getNewListener(() -> {}), WINDOW_EXACT,
- TEST_CALLING_UID);
- setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID);
- setTestAlarm(ELAPSED_REALTIME, 54, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
- TEST_CALLING_UID, null);
- setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null);
-
- setTestAlarmWithListener(ELAPSED_REALTIME, 21, getNewListener(() -> {}), WINDOW_EXACT,
- TEST_CALLING_UID_2);
- setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2);
- setTestAlarm(ELAPSED_REALTIME, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
- TEST_CALLING_UID_2, null);
- setTestAlarm(RTC, 549, 234, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID_2, null);
-
- assertEquals(8, mService.mAlarmStore.size());
-
- mListener.handleUidCachedChanged(TEST_CALLING_UID, true);
- assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
- assertEquals(7, mService.mAlarmStore.size());
-
- mListener.handleUidCachedChanged(TEST_CALLING_UID_2, true);
- assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
- assertEquals(6, mService.mAlarmStore.size());
- }
-
- @Test
- public void alarmCountOnListenerCached() {
- mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);
-
- // Set some alarms for TEST_CALLING_UID.
- final int numExactListenerUid1 = 14;
- for (int i = 0; i < numExactListenerUid1; i++) {
- setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i,
- getNewListener(() -> {}));
- }
- setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID);
- setTestAlarm(ELAPSED_REALTIME, 54, getNewMockPendingIntent());
- setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null);
-
- // Set some alarms for TEST_CALLING_UID_2.
- final int numExactListenerUid2 = 9;
- for (int i = 0; i < numExactListenerUid2; i++) {
- setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i,
- getNewListener(() -> {}), WINDOW_EXACT, TEST_CALLING_UID_2);
- }
- setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2);
- setTestAlarm(RTC_WAKEUP, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
- TEST_CALLING_UID_2, null);
-
- assertEquals(numExactListenerUid1 + 3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
- assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
-
- mListener.handleUidCachedChanged(TEST_CALLING_UID, true);
- assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
- assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
- assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
-
- mListener.handleUidCachedChanged(TEST_CALLING_UID_2, true);
- assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
- assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
- assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
- }
-
private void executeUidFrozenStateCallback(int[] uids, int[] frozenStates) {
assertNotNull(mUidFrozenStateCallback);
mUidFrozenStateCallback.onUidFrozenStateChanged(uids, frozenStates);
}
- @EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
@DisableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS)
@Test
public void exactListenerAlarmsRemovedOnFrozen() {
@@ -3848,7 +3775,6 @@ public final class AlarmManagerServiceTest {
assertEquals(6, mService.mAlarmStore.size());
}
- @EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
@DisableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS)
@Test
public void alarmCountOnListenerFrozen() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index dcbc23410fdb..5a872ea04bcc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -47,10 +47,8 @@ import static com.android.server.am.Flags.FLAG_AVOID_RESOLVING_TYPE;
import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK;
import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE;
import static com.android.server.am.ProcessList.NETWORK_STATE_UNBLOCK;
-
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -80,6 +78,7 @@ import android.Manifest;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.ApplicationThreadConstants;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
import android.app.ForegroundServiceDelegationOptions;
@@ -87,6 +86,7 @@ import android.app.IUidObserver;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.SyncNotedAppOp;
+import android.app.backup.BackupAnnotations;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -111,6 +111,7 @@ import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.permission.IPermissionManager;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -133,6 +134,7 @@ import com.android.server.am.ProcessList.IsolatedUidRange;
import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
import com.android.server.am.UidObserverController.ChangeRecord;
import com.android.server.appop.AppOpsService;
+import com.android.server.job.JobSchedulerInternal;
import com.android.server.notification.NotificationManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerService;
@@ -228,6 +230,7 @@ public class ActivityManagerServiceTest {
@Mock private PackageManagerInternal mPackageManagerInternal;
@Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal;
@Mock private NotificationManagerInternal mNotificationManagerInternal;
+ @Mock private JobSchedulerInternal mJobSchedulerInternal;
@Mock private ContentResolver mContentResolver;
private TestInjector mInjector;
@@ -249,6 +252,7 @@ public class ActivityManagerServiceTest {
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal);
LocalServices.addService(NotificationManagerInternal.class, mNotificationManagerInternal);
+ LocalServices.addService(JobSchedulerInternal.class, mJobSchedulerInternal);
doReturn(new ComponentName("", "")).when(mPackageManagerInternal)
.getSystemUiServiceComponent();
@@ -308,6 +312,7 @@ public class ActivityManagerServiceTest {
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
LocalServices.removeServiceForTest(NotificationManagerInternal.class);
+ LocalServices.removeServiceForTest(JobSchedulerInternal.class);
if (mMockingSession != null) {
mMockingSession.finishMocking();
@@ -1548,6 +1553,50 @@ public class ActivityManagerServiceTest {
eq(notificationId), anyInt());
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void bindBackupAgent_fullBackup_shouldUseRestrictedMode_setsInFullBackup()
+ throws Exception {
+ ActivityManagerService spyAms = spy(mAms);
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = TEST_PACKAGE;
+ applicationInfo.processName = TEST_PACKAGE;
+ applicationInfo.uid = TEST_UID;
+ doReturn(applicationInfo).when(mPackageManager).getApplicationInfo(eq(TEST_PACKAGE),
+ anyLong(), anyInt());
+ ProcessRecord appRec = new ProcessRecord(mAms, applicationInfo, TAG, TEST_UID);
+ doReturn(appRec).when(spyAms).getProcessRecordLocked(eq(TEST_PACKAGE), eq(TEST_UID));
+
+ spyAms.bindBackupAgent(TEST_PACKAGE, ApplicationThreadConstants.BACKUP_MODE_FULL,
+ UserHandle.USER_SYSTEM,
+ BackupAnnotations.BackupDestination.CLOUD, /* shouldUseRestrictedMode= */
+ true);
+
+ assertThat(appRec.isInFullBackup()).isTrue();
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void bindBackupAgent_fullBackup_shouldNotUseRestrictedMode_doesNotSetInFullBackup()
+ throws Exception {
+ ActivityManagerService spyAms = spy(mAms);
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = TEST_PACKAGE;
+ applicationInfo.processName = TEST_PACKAGE;
+ applicationInfo.uid = TEST_UID;
+ doReturn(applicationInfo).when(mPackageManager).getApplicationInfo(eq(TEST_PACKAGE),
+ anyLong(), anyInt());
+ ProcessRecord appRec = new ProcessRecord(mAms, applicationInfo, TAG, TEST_UID);
+ doReturn(appRec).when(spyAms).getProcessRecordLocked(eq(TEST_PACKAGE), eq(TEST_UID));
+
+ spyAms.bindBackupAgent(TEST_PACKAGE, ApplicationThreadConstants.BACKUP_MODE_FULL,
+ UserHandle.USER_SYSTEM,
+ BackupAnnotations.BackupDestination.CLOUD, /* shouldUseRestrictedMode= */
+ false);
+
+ assertThat(appRec.isInFullBackup()).isFalse();
+ }
+
private static class TestHandler extends Handler {
private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec
private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index a1a8b0ec7d2f..5eb23a24908d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -58,6 +58,7 @@ import com.android.server.compat.PlatformCompat;
import com.android.server.wm.ActivityTaskManagerService;
import org.junit.Rule;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -185,10 +186,10 @@ public abstract class BaseBroadcastQueueTest {
doReturn(mAppStartInfoTracker).when(mProcessList).getAppStartInfoTracker();
- doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastFilter.CHANGE_RESTRICT_PRIORITY_VALUES), anyInt());
- doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), anyInt());
+ doReturn(true).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), any(ApplicationInfo.class));
+ doReturn(true).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), any(ApplicationInfo.class));
}
public void tearDown() throws Exception {
@@ -298,7 +299,7 @@ public abstract class BaseBroadcastQueueTest {
filter.setPriority(priority);
final BroadcastFilter res = new BroadcastFilter(filter, receiverList,
receiverList.app.info.packageName, null, null, null, receiverList.uid,
- receiverList.userId, false, false, true,
+ receiverList.userId, false, false, true, receiverList.app.info,
mock(PlatformCompat.class));
receiverList.add(res);
return res;
@@ -308,4 +309,8 @@ public abstract class BaseBroadcastQueueTest {
app.mOptRecord.setPendingFreeze(pendingFreeze);
app.mOptRecord.setFrozen(frozen);
}
+
+ ArgumentMatcher<ApplicationInfo> appInfoEquals(int uid) {
+ return test -> (test.uid == uid);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastFilterTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastFilterTest.java
index e977a7d46f30..5d106ace71dd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastFilterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastFilterTest.java
@@ -20,10 +20,13 @@ import static android.content.IntentFilter.SYSTEM_LOW_PRIORITY;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import android.content.pm.ApplicationInfo;
import android.os.Process;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -53,14 +56,14 @@ public class BroadcastFilterTest {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+
+ doReturn(true).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), any(ApplicationInfo.class));
}
@Test
@EnableFlags(Flags.FLAG_RESTRICT_PRIORITY_VALUES)
public void testCalculateAdjustedPriority() {
- doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastFilter.CHANGE_RESTRICT_PRIORITY_VALUES), anyInt());
-
{
// Pairs of {initial-priority, expected-adjusted-priority}
final Pair<Integer, Integer>[] priorities = new Pair[] {
@@ -95,8 +98,8 @@ public class BroadcastFilterTest {
@Test
@EnableFlags(Flags.FLAG_RESTRICT_PRIORITY_VALUES)
public void testCalculateAdjustedPriority_withChangeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastFilter.CHANGE_RESTRICT_PRIORITY_VALUES), anyInt());
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), any(ApplicationInfo.class));
{
// Pairs of {initial-priority, expected-adjusted-priority}
@@ -132,9 +135,6 @@ public class BroadcastFilterTest {
@Test
@DisableFlags(Flags.FLAG_RESTRICT_PRIORITY_VALUES)
public void testCalculateAdjustedPriority_withFlagDisabled() {
- doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastFilter.CHANGE_RESTRICT_PRIORITY_VALUES), anyInt());
-
{
// Pairs of {initial-priority, expected-adjusted-priority}
final Pair<Integer, Integer>[] priorities = new Pair[] {
@@ -170,7 +170,7 @@ public class BroadcastFilterTest {
@DisableFlags(Flags.FLAG_RESTRICT_PRIORITY_VALUES)
public void testCalculateAdjustedPriority_withFlagDisabled_withChangeIdDisabled() {
doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastFilter.CHANGE_RESTRICT_PRIORITY_VALUES), anyInt());
+ eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), anyInt());
{
// Pairs of {initial-priority, expected-adjusted-priority}
@@ -215,6 +215,7 @@ public class BroadcastFilterTest {
final String errorMsg = String.format("owner=%d; actualPriority=%d; expectedPriority=%d",
owningUid, priority, expectedAdjustedPriority);
assertWithMessage(errorMsg).that(BroadcastFilter.calculateAdjustedPriority(
- owningUid, priority, mPlatformCompat)).isEqualTo(expectedAdjustedPriority);
+ owningUid, priority, mock(ApplicationInfo.class), mPlatformCompat))
+ .isEqualTo(expectedAdjustedPriority);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 1481085c5f71..82237bca2e34 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -47,6 +47,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doAnswer;
@@ -184,7 +185,10 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
}
private static BroadcastFilter makeMockRegisteredReceiver() {
- return mock(BroadcastFilter.class);
+ final BroadcastFilter filter = mock(BroadcastFilter.class);
+ final ApplicationInfo info = makeApplicationInfo(PACKAGE_ORANGE);
+ doReturn(info).when(filter).getApplicationInfo();
+ return filter;
}
private BroadcastRecord makeBroadcastRecord(Intent intent) {
@@ -716,9 +720,9 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
@EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testRunnableAt_Cached_Prioritized_NonDeferrable_changeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE),
- eq(getUidForPackage(PACKAGE_GREEN)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+ argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
final List receivers = List.of(
withPriority(makeManifestReceiver(PACKAGE_RED, PACKAGE_RED), 10),
withPriority(makeManifestReceiver(PACKAGE_GREEN, PACKAGE_GREEN), -10));
@@ -1288,9 +1292,9 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
@SuppressWarnings("GuardedBy")
@Test
public void testDeliveryGroupPolicy_prioritized_diffReceivers_changeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE),
- eq(getUidForPackage(PACKAGE_GREEN)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+ argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF);
@@ -1823,9 +1827,9 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
@SuppressWarnings("GuardedBy")
@Test
public void testDeliveryDeferredForCached_changeIdDisabled() throws Exception {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE),
- eq(getUidForPackage(PACKAGE_GREEN)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+ argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
@@ -2027,9 +2031,9 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
@Test
public void testDeliveryDeferredForCached_withInfiniteDeferred_changeIdDisabled()
throws Exception {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE),
- eq(getUidForPackage(PACKAGE_GREEN)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+ argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 9d92d5fe4f60..ea80f283793e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1659,8 +1659,9 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(receiverBlueApp.uid));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+ argThat(appInfoEquals(receiverBlueApp.uid)));
// Enqueue a normal broadcast that will go to several processes, and
// then enqueue a foreground broadcast that risks reordering
@@ -2471,8 +2472,9 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(receiverBlueApp.uid));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+ argThat(appInfoEquals(receiverBlueApp.uid)));
mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index a424bfdb8df4..8482fd609d05 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -19,12 +19,12 @@ package com.android.server.am;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.server.am.BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE;
import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED;
import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED;
import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING;
import static com.android.server.am.BroadcastRecord.DELIVERY_SKIPPED;
import static com.android.server.am.BroadcastRecord.DELIVERY_TIMEOUT;
+import static com.android.server.am.BroadcastRecord.LIMIT_PRIORITY_SCOPE;
import static com.android.server.am.BroadcastRecord.calculateBlockedUntilBeyondCount;
import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive;
import static com.android.server.am.BroadcastRecord.calculateUrgent;
@@ -35,7 +35,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import android.app.BackgroundStartPrivileges;
@@ -63,6 +64,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
@@ -108,8 +110,8 @@ public class BroadcastRecordTest {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), anyInt());
+ doReturn(true).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), any(ApplicationInfo.class));
}
@Test
@@ -222,8 +224,8 @@ public class BroadcastRecordTest {
@EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testIsPrioritized_withDifferentPriorities_withFirstUidChangeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
assertTrue(isPrioritized(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -256,8 +258,8 @@ public class BroadcastRecordTest {
@EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testIsPrioritized_withDifferentPriorities_withLastUidChangeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(3)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(3))));
assertTrue(isPrioritized(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -294,8 +296,8 @@ public class BroadcastRecordTest {
@EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testIsPrioritized_withDifferentPriorities_withUidChangeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(2))));
assertTrue(isPrioritized(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -328,10 +330,10 @@ public class BroadcastRecordTest {
@EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testIsPrioritized_withDifferentPriorities_withMultipleUidChangeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(2))));
assertTrue(isPrioritized(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -362,10 +364,10 @@ public class BroadcastRecordTest {
assertArrayEquals(new int[] {0, 0, 1, 1, 3},
calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 20),
- createResolveInfo(PACKAGE2, getAppId(3), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 20),
createResolveInfo(PACKAGE3, getAppId(3), 10),
createResolveInfo(PACKAGE3, getAppId(3), 0),
- createResolveInfo(PACKAGE3, getAppId(2), 0)), false, mPlatformCompat));
+ createResolveInfo(PACKAGE2, getAppId(2), 0)), false, mPlatformCompat));
}
@Test
@@ -592,8 +594,8 @@ public class BroadcastRecordTest {
@EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testSetDeliveryState_DeferUntilActive_changeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
final BroadcastRecord r = createBroadcastRecord(
new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -960,8 +962,8 @@ public class BroadcastRecordTest {
createResolveInfo(PACKAGE2, getAppId(2)),
createResolveInfo(PACKAGE3, getAppId(3)))));
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
assertArrayEquals(new boolean[] {false, true, true}, calculateChangeState(
List.of(createResolveInfo(PACKAGE1, getAppId(1)),
createResolveInfo(PACKAGE2, getAppId(2)),
@@ -969,11 +971,11 @@ public class BroadcastRecordTest {
assertArrayEquals(new boolean[] {false, true, false, true}, calculateChangeState(
List.of(createResolveInfo(PACKAGE1, getAppId(1)),
createResolveInfo(PACKAGE2, getAppId(2)),
- createResolveInfo(PACKAGE2, getAppId(1)),
+ createResolveInfo(PACKAGE1, getAppId(1)),
createResolveInfo(PACKAGE3, getAppId(3)))));
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(2))));
assertArrayEquals(new boolean[] {false, false, true}, calculateChangeState(
List.of(createResolveInfo(PACKAGE1, getAppId(1)),
createResolveInfo(PACKAGE2, getAppId(2)),
@@ -987,8 +989,8 @@ public class BroadcastRecordTest {
createResolveInfo(PACKAGE2, getAppId(2)),
createResolveInfo(PACKAGE3, getAppId(3)))));
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(3)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(3))));
assertArrayEquals(new boolean[] {false, false, false}, calculateChangeState(
List.of(createResolveInfo(PACKAGE1, getAppId(1)),
createResolveInfo(PACKAGE2, getAppId(2)),
@@ -998,14 +1000,14 @@ public class BroadcastRecordTest {
List.of(createResolveInfo(PACKAGE1, getAppId(1)),
createResolveInfo(PACKAGE3, getAppId(3)),
createResolveInfo(PACKAGE2, getAppId(2)),
- createResolveInfo(PACKAGE2, getAppId(1)),
+ createResolveInfo(PACKAGE1, getAppId(1)),
createResolveInfo(PACKAGE2, getAppId(2)),
createResolveInfo(PACKAGE3, getAppId(3)))));
}
private boolean[] calculateChangeState(List<Object> receivers) {
return BroadcastRecord.calculateChangeStateForReceivers(receivers,
- CHANGE_LIMIT_PRIORITY_SCOPE, mPlatformCompat);
+ LIMIT_PRIORITY_SCOPE, mPlatformCompat);
}
private static void cleanupDisabledPackageReceivers(BroadcastRecord record,
@@ -1185,4 +1187,8 @@ public class BroadcastRecordTest {
assertEquals("deferred", expectedDeferredCount, r.deferredCount);
assertEquals("beyond", expectedBeyondCount, r.beyondCount);
}
+
+ private ArgumentMatcher<ApplicationInfo> appInfoEquals(int uid) {
+ return test -> (test.uid == uid);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index f82a86092064..a9569b4096ff 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -1427,6 +1427,31 @@ public class MockingOomAdjusterTests {
@SuppressWarnings("GuardedBy")
@Test
+ public void testUpdateOomAdj_DoOne_Service_NotPerceptible_AboveClient() {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+ ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ ProcessRecord service = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+ MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+ bindService(app, client, null, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
+ bindService(service, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
+ mProcessStateController.setRunningRemoteAnimation(client, true);
+ mProcessStateController.updateHasAboveClientLocked(app.mServices);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ updateOomAdj(client, app, service);
+
+ final int expectedAdj;
+ if (Flags.addModifyRawOomAdjServiceLevel()) {
+ expectedAdj = SERVICE_ADJ;
+ } else {
+ expectedAdj = CACHED_APP_MIN_ADJ;
+ }
+ assertEquals(expectedAdj, app.mState.getSetAdj());
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
public void testUpdateOomAdj_DoOne_Service_NotVisible() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
@@ -2906,7 +2931,7 @@ public class MockingOomAdjusterTests {
// Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and
// verify that its OOM adjustment level is unaffected.
bindService(service, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
- app.mServices.updateHasAboveClientLocked();
+ mProcessStateController.updateHasAboveClientLocked(app.mServices);
assertTrue(app.mServices.hasAboveClient());
updateOomAdj(app);
@@ -2928,7 +2953,7 @@ public class MockingOomAdjusterTests {
// Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and
// verify that its OOM adjustment level is unaffected.
bindService(app, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
- app.mServices.updateHasAboveClientLocked();
+ mProcessStateController.updateHasAboveClientLocked(app.mServices);
assertFalse(app.mServices.hasAboveClient());
updateOomAdj(app);
@@ -2983,7 +3008,7 @@ public class MockingOomAdjusterTests {
// Since sr.app is null, this service cannot be in the same process as the
// client so we expect the BIND_ABOVE_CLIENT adjustment to take effect.
- app.mServices.updateHasAboveClientLocked();
+ mProcessStateController.updateHasAboveClientLocked(app.mServices);
updateOomAdj(app);
assertTrue(app.mServices.hasAboveClient());
assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
@@ -3306,7 +3331,7 @@ public class MockingOomAdjusterTests {
if (Flags.pushGlobalStateToOomadjuster()) {
mProcessStateController.setBackupTarget(app, app.userId);
} else {
- BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0);
+ BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0, true);
backupTarget.app = app;
doReturn(backupTarget).when(mService.mBackupTargets).get(anyInt());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 65286d9aabc7..07f2188d30eb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -18,9 +18,7 @@ package com.android.server.backup;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
import static com.google.common.truth.Truth.assertThat;
-
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -32,20 +30,27 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.ApplicationThreadConstants;
+import android.app.IActivityManager;
import android.app.backup.BackupAgent;
-import android.app.backup.BackupAnnotations;
import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.testing.TestableContext;
import android.util.FeatureFlagUtils;
@@ -68,7 +73,9 @@ import com.google.common.collect.ImmutableSet;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -77,8 +84,12 @@ import org.mockito.quality.Strictness;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
import java.util.function.IntConsumer;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
@Presubmit
@RunWith(AndroidJUnit4.class)
public class UserBackupManagerServiceTest {
@@ -88,6 +99,11 @@ public class UserBackupManagerServiceTest {
private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 100;
@UserIdInt private static final int USER_ID = 0;
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock IBackupManagerMonitor mBackupManagerMonitor;
@Mock IBackupObserver mBackupObserver;
@Mock PackageManager mPackageManager;
@@ -99,10 +115,14 @@ public class UserBackupManagerServiceTest {
@Mock JobScheduler mJobScheduler;
@Mock BackupHandler mBackupHandler;
@Mock BackupManagerMonitorEventSender mBackupManagerMonitorEventSender;
+ @Mock IActivityManager mActivityManager;
+ @Mock
+ ActivityManagerInternal mActivityManagerInternal;
private TestableContext mContext;
private MockitoSession mSession;
private TestBackupService mService;
+ private ApplicationInfo mTestPackageApplicationInfo;
@Before
public void setUp() throws Exception {
@@ -120,12 +140,14 @@ public class UserBackupManagerServiceTest {
mContext.getTestablePermissions().setPermission(android.Manifest.permission.BACKUP,
PackageManager.PERMISSION_GRANTED);
- mService = new TestBackupService(mContext, mPackageManager, mOperationStorage,
- mTransportManager, mBackupHandler);
+ mService = new TestBackupService();
mService.setEnabled(true);
mService.setSetupComplete(true);
mService.enqueueFullBackup("com.test.backup.app", /* lastBackedUp= */ 0);
- }
+
+ mTestPackageApplicationInfo = new ApplicationInfo();
+ mTestPackageApplicationInfo.packageName = TEST_PACKAGE;
+ }
@After
public void tearDown() {
@@ -298,9 +320,160 @@ public class UserBackupManagerServiceTest {
new DataTypeResult(/* dataType */ "type_2"));
mService.reportDelayedRestoreResult(TEST_PACKAGE, results);
-
verify(mBackupManagerMonitorEventSender).sendAgentLoggingResults(
- eq(packageInfo), eq(results), eq(BackupAnnotations.OperationType.RESTORE));
+ eq(packageInfo), eq(results), eq(OperationType.RESTORE));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_restrictedModeChangesFlagOff_shouldUseRestrictedMode()
+ throws Exception {
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ // Make sure we never hit the code that checks the property.
+ verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_keyValueBackup_shouldNotUseRestrictedMode()
+ throws Exception {
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ // Make sure we never hit the code that checks the property.
+ verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_keyValueRestore_shouldNotUseRestrictedMode()
+ throws Exception {
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_RESTORE, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ // Make sure we never hit the code that checks the property.
+ verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_packageOptedIn_shouldUseRestrictedMode()
+ throws Exception {
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+ eq(TEST_PACKAGE), any(), anyInt())).thenReturn(new PackageManager.Property(
+ PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ true,
+ TEST_PACKAGE, /* className= */ null));
+
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_packageOptedOut_shouldNotUseRestrictedMode()
+ throws Exception {
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+ eq(TEST_PACKAGE), any(), anyInt())).thenReturn(new PackageManager.Property(
+ PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ false,
+ TEST_PACKAGE, /* className= */ null));
+
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @DisableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_targetSdkBelowB_shouldUseRestrictedMode()
+ throws Exception {
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+ eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException()
+ );
+
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_targetSdkB_notInList_shouldUseRestrictedMode()
+ throws Exception {
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+ eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException()
+ );
+ mService.clearNoRestrictedModePackages();
+
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_forRestore_targetSdkB_inList_shouldNotUseRestrictedMode()
+ throws Exception {
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+ eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException()
+ );
+ mService.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.RESTORE);
+
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_forBackup_targetSdkB_inList_shouldNotUseRestrictedMode()
+ throws Exception {
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+ eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException()
+ );
+ mService.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.BACKUP);
+
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
}
private static PackageInfo getPackageInfo(String packageName) {
@@ -316,11 +489,9 @@ public class UserBackupManagerServiceTest {
private volatile Thread mWorkerThread = null;
- TestBackupService(Context context, PackageManager packageManager,
- LifecycleOperationStorage operationStorage, TransportManager transportManager,
- BackupHandler backupHandler) {
- super(context, packageManager, operationStorage, transportManager, backupHandler,
- createConstants(context));
+ TestBackupService() {
+ super(mContext, mPackageManager, mOperationStorage, mTransportManager, mBackupHandler,
+ createConstants(mContext), mActivityManager, mActivityManagerInternal);
}
private static BackupManagerConstants createConstants(Context context) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
index 94742537ed1a..331057398949 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
@@ -18,34 +18,95 @@ package com.android.server.backup.fullbackup;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.when;
+import android.app.backup.BackupAnnotations;
+import android.app.backup.BackupTransport;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.OperationStorage;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.transport.BackupTransportClient;
+import com.android.server.backup.transport.TransportConnection;
+import com.android.server.backup.utils.BackupEligibilityRules;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
@Presubmit
@RunWith(AndroidJUnit4.class)
public class PerformFullTransportBackupTaskTest {
+ private static final String TEST_PACKAGE_1 = "package1";
+ private static final String TEST_PACKAGE_2 = "package2";
+
+ @Mock
+ BackupAgentTimeoutParameters mBackupAgentTimeoutParameters;
+ @Mock
+ BackupEligibilityRules mBackupEligibilityRules;
@Mock
UserBackupManagerService mBackupManagerService;
@Mock
+ BackupTransportClient mBackupTransportClient;
+ @Mock
+ CountDownLatch mLatch;
+ @Mock
+ OperationStorage mOperationStorage;
+ @Mock
+ PackageManager mPackageManager;
+ @Mock
+ TransportConnection mTransportConnection;
+ @Mock
TransportManager mTransportManager;
+ @Mock
+ UserBackupManagerService.BackupWakeLock mWakeLock;
+
+ private final List<String> mEligiblePackages = new ArrayList<>();
+
+ private PerformFullTransportBackupTask mTask;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ when(mBackupManagerService.getPackageManager()).thenReturn(mPackageManager);
+ when(mBackupManagerService.getQueueLock()).thenReturn("something!");
+ when(mBackupManagerService.isEnabled()).thenReturn(true);
+ when(mBackupManagerService.getWakelock()).thenReturn(mWakeLock);
+ when(mBackupManagerService.isSetupComplete()).thenReturn(true);
+ when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn(
+ mBackupAgentTimeoutParameters);
when(mBackupManagerService.getTransportManager()).thenReturn(mTransportManager);
+ when(mTransportManager.getCurrentTransportClient(any())).thenReturn(mTransportConnection);
+ when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransportClient);
+ when(mTransportConnection.connect(any())).thenReturn(mBackupTransportClient);
+ when(mBackupTransportClient.performFullBackup(any(), any(), anyInt())).thenReturn(
+ BackupTransport.TRANSPORT_ERROR);
+ when(mBackupEligibilityRules.appIsEligibleForBackup(
+ argThat(app -> mEligiblePackages.contains(app.packageName)))).thenReturn(
+ true);
+ when(mBackupEligibilityRules.appGetsFullBackup(
+ argThat(app -> mEligiblePackages.contains(app.packageName)))).thenReturn(
+ true);
}
@Test
@@ -70,4 +131,49 @@ public class PerformFullTransportBackupTaskTest {
/* backupEligibilityRules */ null);
});
}
+
+ @Test
+ public void run_setsAndClearsNoRestrictedModePackages() throws Exception {
+ mockPackageEligibleForFullBackup(TEST_PACKAGE_1);
+ mockPackageEligibleForFullBackup(TEST_PACKAGE_2);
+ createTask(new String[] {TEST_PACKAGE_1, TEST_PACKAGE_2});
+ when(mBackupTransportClient.getPackagesThatShouldNotUseRestrictedMode(any(),
+ anyInt())).thenReturn(Set.of("package1"));
+
+ mTask.run();
+
+ InOrder inOrder = inOrder(mBackupManagerService);
+ inOrder.verify(mBackupManagerService).setNoRestrictedModePackages(
+ eq(Set.of("package1")),
+ eq(BackupAnnotations.OperationType.BACKUP));
+ inOrder.verify(mBackupManagerService).clearNoRestrictedModePackages();
+ }
+
+ private void createTask(String[] packageNames) {
+ mTask = PerformFullTransportBackupTask
+ .newWithCurrentTransport(
+ mBackupManagerService,
+ mOperationStorage,
+ /* observer */ null,
+ /* whichPackages */ packageNames,
+ /* updateSchedule */ false,
+ /* runningJob */ null,
+ mLatch,
+ /* backupObserver */ null,
+ /* monitor */ null,
+ /* userInitiated */ false,
+ /* caller */ null,
+ mBackupEligibilityRules);
+ }
+
+ private void mockPackageEligibleForFullBackup(String packageName) throws Exception {
+ mEligiblePackages.add(packageName);
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.packageName = packageName;
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = packageName;
+ packageInfo.applicationInfo = appInfo;
+ when(mPackageManager.getPackageInfoAsUser(eq(packageName), anyInt(), anyInt())).thenReturn(
+ packageInfo);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
index 414532b88e22..055adf68ee0f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -23,8 +23,10 @@ import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.backup.BackupAnnotations;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupTransport;
@@ -91,6 +93,8 @@ public class PerformUnifiedRestoreTaskTest {
private UserBackupManagerService mBackupManagerService;
@Mock
private TransportConnection mTransportConnection;
+ @Mock
+ private BackupTransportClient mBackupTransportClient;
private Set<String> mExcludedkeys = new HashSet<>();
private Map<String, String> mBackupData = new HashMap<>();
@@ -151,6 +155,23 @@ public class PerformUnifiedRestoreTaskTest {
}
@Test
+ public void setNoRestrictedModePackages_callsTransportAndSetsValue() throws Exception {
+ PackageInfo packageInfo1 = new PackageInfo();
+ packageInfo1.packageName = "package1";
+ PackageInfo packageInfo2 = new PackageInfo();
+ packageInfo2.packageName = "package2";
+ when(mBackupTransportClient.getPackagesThatShouldNotUseRestrictedMode(any(),
+ anyInt())).thenReturn(Set.of("package1"));
+
+ mRestoreTask.setNoRestrictedModePackages(mBackupTransportClient,
+ new PackageInfo[]{packageInfo1, packageInfo2});
+
+ verify(mBackupManagerService).setNoRestrictedModePackages(
+ eq(Set.of("package1")),
+ eq(BackupAnnotations.OperationType.RESTORE));
+ }
+
+ @Test
public void testFilterExcludedKeys() throws Exception {
when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME)))
.thenReturn(mExcludedkeys);
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
index 2d7d46f83c47..13e32078f609 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
@@ -19,7 +19,14 @@ package com.android.server.backup.transport;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
-
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+
+import android.app.backup.BackupAnnotations.OperationType;
import android.app.backup.BackupTransport;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.RestoreDescription;
@@ -38,15 +45,31 @@ import com.android.internal.backup.IBackupTransport;
import com.android.internal.backup.ITransportStatusCallback;
import com.android.internal.infra.AndroidFuture;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.List;
+import java.util.Set;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class BackupTransportClientTest {
+ @Mock
+ IBackupTransport mMockBackupTransport;
+
+ private BackupTransportClient mMockingTransportClient;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mMockingTransportClient = new BackupTransportClient(
+ mMockBackupTransport);
+ }
+
private static class TestFuturesFakeTransportBinder extends FakeTransportBinderBase {
public final Object mLock = new Object();
@@ -128,6 +151,70 @@ public class BackupTransportClientTest {
thread.join();
}
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_passesSetAsListToBinder()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(List.of("package1", "package2"));
+
+ mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of("package1", "package2"),
+ OperationType.BACKUP);
+
+ verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(
+ argThat(list -> Set.copyOf(list).equals(Set.of("package1", "package2"))),
+ eq(OperationType.BACKUP), any());
+ }
+
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_forRestore_callsBinderForRestore()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(null);
+
+ mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of(),
+ OperationType.RESTORE);
+
+ verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(),
+ eq(OperationType.RESTORE), any());
+ }
+
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_forBackup_callsBinderForBackup()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(null);
+
+ mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of(),
+ OperationType.BACKUP);
+
+ verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(),
+ eq(OperationType.BACKUP), any());
+ }
+
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_nullResult_returnsEmptySet()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(null);
+
+ Set<String> result = mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of(),
+ OperationType.BACKUP);
+
+ assertThat(result).isEqualTo(Set.of());
+ }
+
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_returnsResultAsSet()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(List.of("package1", "package2"));
+
+ Set<String> result = mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of("package1", "package2"),
+ OperationType.BACKUP);
+
+ assertThat(result).isEqualTo(Set.of("package1", "package2"));
+ }
+
private static class TestCallbacksFakeTransportBinder extends FakeTransportBinderBase {
public final Object mLock = new Object();
@@ -158,7 +245,6 @@ public class BackupTransportClientTest {
assertThat(status).isEqualTo(123);
}
-
@Test
public void testFinishBackup_completesLater_returnsStatus() throws Exception {
TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder();
@@ -211,6 +297,14 @@ public class BackupTransportClientTest {
thread.join();
}
+ private void mockGetPackagesThatShouldNotUseRestrictedModeReturn(List<String> returnList)
+ throws Exception {
+ doAnswer(
+ i -> ((AndroidFuture<List<String>>) i.getArguments()[2]).complete(returnList)).when(
+ mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(), anyInt(),
+ any());
+ }
+
// Convenience layer so we only need to fake specific methods useful for each test case.
private static class FakeTransportBinderBase implements IBackupTransport {
@Override public void name(AndroidFuture<String> f) throws RemoteException {}
@@ -258,6 +352,10 @@ public class BackupTransportClientTest {
@Override
public void getBackupManagerMonitor(AndroidFuture<IBackupManagerMonitor> resultFuture)
throws RemoteException {}
+ @Override
+ public void getPackagesThatShouldNotUseRestrictedMode(List<String> packageNames,
+ int operationType, AndroidFuture<List<String>> resultFuture)
+ throws RemoteException {}
@Override public IBinder asBinder() {
return null;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
index f6c644e3d4d4..20ac0781e2ed 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.content.Context;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.parsing.ApkLite;
import android.content.pm.parsing.ApkLiteParseUtils;
@@ -34,6 +35,8 @@ import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
import android.os.OutcomeReceiver;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -71,13 +74,17 @@ public class InstallDependencyHelperTest {
private static final String PUSH_FILE_DIR = "/data/local/tmp/tests/smockingservicestest/pm/";
private static final String TEST_APP_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk";
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
@Mock private SharedLibrariesImpl mSharedLibraries;
+ @Mock private Context mContext;
+ @Mock private Computer mComputer;
private InstallDependencyHelper mInstallDependencyHelper;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mInstallDependencyHelper = new InstallDependencyHelper(mSharedLibraries);
+ mInstallDependencyHelper = new InstallDependencyHelper(mContext, mSharedLibraries);
}
@Test
@@ -88,7 +95,8 @@ public class InstallDependencyHelperTest {
PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
- mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+ 0, mHandler, callback);
callback.assertFailure();
assertThat(callback.error).hasMessageThat().contains("xyz");
@@ -104,11 +112,12 @@ public class InstallDependencyHelperTest {
.thenReturn(missingDependency);
CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
- mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+ 0, mHandler, callback);
callback.assertFailure();
assertThat(callback.error).hasMessageThat().contains(
- "Failed to bind to Dependency Installer");
+ "Dependency Installer Service not found");
}
@@ -121,7 +130,8 @@ public class InstallDependencyHelperTest {
.thenReturn(missingDependency);
CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ true);
- mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+ 0, mHandler, callback);
callback.assertSuccess();
}
diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 58489f398775..0881b4cf9bcf 100644
--- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -18,6 +18,7 @@ package com.android.server.power.hint;
import static com.android.server.power.hint.HintManagerService.CLEAN_UP_UID_DELAY_MILLIS;
+import static com.android.server.power.hint.HintManagerService.DEFAULT_HEADROOM_PID;
import static com.google.common.truth.Truth.assertThat;
@@ -51,11 +52,15 @@ import android.content.pm.PackageManager;
import android.hardware.common.fmq.MQDescriptor;
import android.hardware.power.ChannelConfig;
import android.hardware.power.ChannelMessage;
+import android.hardware.power.CpuHeadroomParams;
+import android.hardware.power.GpuHeadroomParams;
import android.hardware.power.IPower;
import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag;
import android.hardware.power.WorkDuration;
import android.os.Binder;
+import android.os.CpuHeadroomParamsInternal;
+import android.os.GpuHeadroomParamsInternal;
import android.os.IBinder;
import android.os.IHintSession;
import android.os.PerformanceHintManager;
@@ -128,11 +133,11 @@ public class HintManagerServiceTest {
private static final long[] TIMESTAMPS_ZERO = new long[] {};
private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L};
private static final WorkDuration[] WORK_DURATIONS_FIVE = new WorkDuration[] {
- makeWorkDuration(1L, 11L, 1L, 8L, 4L),
- makeWorkDuration(2L, 13L, 2L, 8L, 6L),
- makeWorkDuration(3L, 333333333L, 3L, 8L, 333333333L),
- makeWorkDuration(2L, 13L, 2L, 0L, 6L),
- makeWorkDuration(2L, 13L, 2L, 8L, 0L),
+ makeWorkDuration(1L, 11L, 1L, 8L, 4L),
+ makeWorkDuration(2L, 13L, 2L, 8L, 6L),
+ makeWorkDuration(3L, 333333333L, 3L, 8L, 333333333L),
+ makeWorkDuration(2L, 13L, 2L, 0L, 6L),
+ makeWorkDuration(2L, 13L, 2L, 8L, 0L),
};
private static final String TEST_APP_NAME = "com.android.test.app";
@@ -187,17 +192,17 @@ public class HintManagerServiceTest {
when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID),
eq(SESSION_TIDS_A), eq(DEFAULT_TARGET_DURATION), anyInt(),
any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[0],
- SESSION_IDS[0]));
+ SESSION_IDS[0]));
when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID),
eq(SESSION_TIDS_B), eq(DOUBLED_TARGET_DURATION), anyInt(),
any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[1],
- SESSION_IDS[1]));
+ SESSION_IDS[1]));
when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID),
eq(SESSION_TIDS_C), eq(0L), anyInt(),
any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[2],
- SESSION_IDS[2]));
+ SESSION_IDS[2]));
- when(mIPowerMock.getInterfaceVersion()).thenReturn(5);
+ when(mIPowerMock.getInterfaceVersion()).thenReturn(6);
when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
@@ -217,8 +222,8 @@ public class HintManagerServiceTest {
when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_C),
eq(0L))).thenReturn(SESSION_PTRS[2]);
when(mNativeWrapperMock.halCreateHintSessionWithConfig(anyInt(), anyInt(),
- any(int[].class), anyLong(), anyInt(),
- any(SessionConfig.class))).thenThrow(new UnsupportedOperationException());
+ any(int[].class), anyLong(), anyInt(),
+ any(SessionConfig.class))).thenThrow(new UnsupportedOperationException());
}
static class NativeWrapperFake extends NativeWrapper {
@@ -337,7 +342,7 @@ public class HintManagerServiceTest {
SESSION_TIDS_C, 0L, SessionTag.OTHER, new SessionConfig());
assertNotNull(c);
verify(mNativeWrapperMock, times(3)).halCreateHintSession(anyInt(), anyInt(),
- any(int[].class), anyLong());
+ any(int[].class), anyLong());
}
@Test
@@ -487,7 +492,7 @@ public class HintManagerServiceTest {
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
.createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION,
- SessionTag.OTHER, new SessionConfig());
+ SessionTag.OTHER, new SessionConfig());
a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
verify(mNativeWrapperMock, times(1)).halSendHint(anyLong(),
@@ -514,7 +519,7 @@ public class HintManagerServiceTest {
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
.createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION,
- SessionTag.OTHER, new SessionConfig());
+ SessionTag.OTHER, new SessionConfig());
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
@@ -1096,4 +1101,157 @@ public class HintManagerServiceTest {
verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
assertTrue(service.hasChannel(TGID, UID));
}
+
+ @Test
+ public void testHeadroomPowerHalNotSupported() throws Exception {
+ when(mIPowerMock.getInterfaceVersion()).thenReturn(5);
+ HintManagerService service = createService();
+ assertThrows(UnsupportedOperationException.class, () -> {
+ service.getBinderServiceInstance().getCpuHeadroom(null);
+ });
+ assertThrows(UnsupportedOperationException.class, () -> {
+ service.getBinderServiceInstance().getGpuHeadroom(null);
+ });
+ assertThrows(UnsupportedOperationException.class, () -> {
+ service.getBinderServiceInstance().getCpuHeadroomMinIntervalMillis();
+ });
+ assertThrows(UnsupportedOperationException.class, () -> {
+ service.getBinderServiceInstance().getGpuHeadroomMinIntervalMillis();
+ });
+ }
+
+ @Test
+ public void testCpuHeadroomCache() throws Exception {
+ when(mIPowerMock.getCpuHeadroomMinIntervalMillis()).thenReturn(2000L);
+ CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal();
+ CpuHeadroomParams halParams1 = new CpuHeadroomParams();
+ halParams1.calculationType = CpuHeadroomParams.CalculationType.MIN;
+ halParams1.selectionType = CpuHeadroomParams.SelectionType.ALL;
+ halParams1.pid = Process.myPid();
+
+ CpuHeadroomParamsInternal params2 = new CpuHeadroomParamsInternal();
+ params2.usesDeviceHeadroom = true;
+ params2.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+ params2.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
+ CpuHeadroomParams halParams2 = new CpuHeadroomParams();
+ halParams2.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+ halParams2.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
+ halParams2.pid = DEFAULT_HEADROOM_PID;
+
+ float[] headroom1 = new float[] {0.1f};
+ when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(headroom1);
+ float[] headroom2 = new float[] {0.1f, 0.5f};
+ when(mIPowerMock.getCpuHeadroom(eq(halParams2))).thenReturn(headroom2);
+
+ HintManagerService service = createService();
+ clearInvocations(mIPowerMock);
+
+ service.getBinderServiceInstance().getCpuHeadroomMinIntervalMillis();
+ verify(mIPowerMock, times(0)).getCpuHeadroomMinIntervalMillis();
+ service.getBinderServiceInstance().getCpuHeadroom(params1);
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
+ service.getBinderServiceInstance().getCpuHeadroom(params2);
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2));
+
+ // verify cache is working
+ clearInvocations(mIPowerMock);
+ assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
+ 0.01f);
+ assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
+ 0.01f);
+ verify(mIPowerMock, times(0)).getCpuHeadroom(any());
+
+ // after 1 more second it should be served with cache still
+ Thread.sleep(1000);
+ clearInvocations(mIPowerMock);
+ assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
+ 0.01f);
+ assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
+ 0.01f);
+ verify(mIPowerMock, times(0)).getCpuHeadroom(any());
+
+ // after 1.5 more second it should be served with cache still as timer reset
+ Thread.sleep(1500);
+ clearInvocations(mIPowerMock);
+ assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
+ 0.01f);
+ assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
+ 0.01f);
+ verify(mIPowerMock, times(0)).getCpuHeadroom(any());
+
+ // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
+ Thread.sleep(2100);
+ clearInvocations(mIPowerMock);
+ assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
+ 0.01f);
+ assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
+ 0.01f);
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2));
+ }
+
+ @Test
+ public void testGpuHeadroomCache() throws Exception {
+ when(mIPowerMock.getGpuHeadroomMinIntervalMillis()).thenReturn(2000L);
+ GpuHeadroomParamsInternal params1 = new GpuHeadroomParamsInternal();
+ GpuHeadroomParams halParams1 = new GpuHeadroomParams();
+ halParams1.calculationType = GpuHeadroomParams.CalculationType.MIN;
+
+ GpuHeadroomParamsInternal params2 = new GpuHeadroomParamsInternal();
+ GpuHeadroomParams halParams2 = new GpuHeadroomParams();
+ params2.calculationType =
+ halParams2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
+
+ float headroom1 = 0.1f;
+ when(mIPowerMock.getGpuHeadroom(eq(halParams1))).thenReturn(headroom1);
+ float headroom2 = 0.2f;
+ when(mIPowerMock.getGpuHeadroom(eq(halParams2))).thenReturn(headroom2);
+ HintManagerService service = createService();
+ clearInvocations(mIPowerMock);
+
+ service.getBinderServiceInstance().getGpuHeadroomMinIntervalMillis();
+ verify(mIPowerMock, times(0)).getGpuHeadroomMinIntervalMillis();
+ assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
+ 0.01f);
+ assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
+ 0.01f);
+ verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
+
+ // verify cache is working
+ clearInvocations(mIPowerMock);
+ assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
+ 0.01f);
+ assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
+ 0.01f);
+ verify(mIPowerMock, times(0)).getGpuHeadroom(any());
+
+ // after 1 more second it should be served with cache still
+ Thread.sleep(1000);
+ clearInvocations(mIPowerMock);
+ assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
+ 0.01f);
+ assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
+ 0.01f);
+ verify(mIPowerMock, times(0)).getGpuHeadroom(any());
+
+ // after 1.5 more second it should be served with cache still as timer reset
+ Thread.sleep(1500);
+ clearInvocations(mIPowerMock);
+ assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
+ 0.01f);
+ assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
+ 0.01f);
+ verify(mIPowerMock, times(0)).getGpuHeadroom(any());
+
+ // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
+ Thread.sleep(2100);
+ clearInvocations(mIPowerMock);
+ assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
+ 0.01f);
+ assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
+ 0.01f);
+ verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
+ }
}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index 359cf6376239..b48c2d7f5007 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -2705,12 +2705,11 @@ public class PowerManagerServiceTest {
verify(mInvalidateInteractiveCachesMock).call();
listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
- verify(mInvalidateInteractiveCachesMock, times(2)).call();
mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP,
mClock.now(), 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
- verify(mInvalidateInteractiveCachesMock, times(3)).call();
+ verify(mInvalidateInteractiveCachesMock, times(2)).call();
}
@Test
@@ -2732,12 +2731,11 @@ public class PowerManagerServiceTest {
verify(mInvalidateInteractiveCachesMock).call();
listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
- verify(mInvalidateInteractiveCachesMock, times(2)).call();
mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, mClock.now(),
0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
- verify(mInvalidateInteractiveCachesMock, times(3)).call();
+ verify(mInvalidateInteractiveCachesMock, times(2)).call();
}
@Test
diff --git a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
index 40e00344f87a..0da6db634330 100644
--- a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
+++ b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
@@ -16,9 +16,13 @@
package com.android.server.security.forensic;
+import static android.Manifest.permission.MANAGE_FORENSIC_STATE;
+import static android.Manifest.permission.READ_FORENSIC_STATE;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -29,7 +33,9 @@ import static org.mockito.Mockito.verify;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Looper;
+import android.os.PermissionEnforcer;
import android.os.RemoteException;
+import android.os.test.FakePermissionEnforcer;
import android.os.test.TestLooper;
import android.security.forensic.ForensicEvent;
import android.security.forensic.IForensicServiceCommandCallback;
@@ -41,6 +47,7 @@ import androidx.test.core.app.ApplicationProvider;
import com.android.server.ServiceThread;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -50,34 +57,36 @@ import java.util.Map;
public class ForensicServiceTest {
private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN;
- private static final int STATE_INVISIBLE = IForensicServiceStateCallback.State.INVISIBLE;
- private static final int STATE_VISIBLE = IForensicServiceStateCallback.State.VISIBLE;
+ private static final int STATE_DISABLED = IForensicServiceStateCallback.State.DISABLED;
private static final int STATE_ENABLED = IForensicServiceStateCallback.State.ENABLED;
private static final int ERROR_UNKNOWN = IForensicServiceCommandCallback.ErrorCode.UNKNOWN;
private static final int ERROR_PERMISSION_DENIED =
IForensicServiceCommandCallback.ErrorCode.PERMISSION_DENIED;
- private static final int ERROR_INVALID_STATE_TRANSITION =
- IForensicServiceCommandCallback.ErrorCode.INVALID_STATE_TRANSITION;
- private static final int ERROR_BACKUP_TRANSPORT_UNAVAILABLE =
- IForensicServiceCommandCallback.ErrorCode.BACKUP_TRANSPORT_UNAVAILABLE;
+ private static final int ERROR_TRANSPORT_UNAVAILABLE =
+ IForensicServiceCommandCallback.ErrorCode.TRANSPORT_UNAVAILABLE;
private static final int ERROR_DATA_SOURCE_UNAVAILABLE =
IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE;
private Context mContext;
- private BackupTransportConnection mBackupTransportConnection;
+ private ForensicEventTransportConnection mForensicEventTransportConnection;
private DataAggregator mDataAggregator;
private ForensicService mForensicService;
private TestLooper mTestLooper;
private Looper mLooper;
private TestLooper mTestLooperOfDataAggregator;
private Looper mLooperOfDataAggregator;
+ private FakePermissionEnforcer mPermissionEnforcer;
@SuppressLint("VisibleForTests")
@Before
public void setUp() {
mContext = spy(ApplicationProvider.getApplicationContext());
+ mPermissionEnforcer = new FakePermissionEnforcer();
+ mPermissionEnforcer.grant(READ_FORENSIC_STATE);
+ mPermissionEnforcer.grant(MANAGE_FORENSIC_STATE);
+
mTestLooper = new TestLooper();
mLooper = mTestLooper.getLooper();
mTestLooperOfDataAggregator = new TestLooper();
@@ -87,217 +96,101 @@ public class ForensicServiceTest {
}
@Test
- public void testMonitorState_Invisible() throws RemoteException {
+ public void testAddStateCallback_NoPermission() {
+ mPermissionEnforcer.revoke(READ_FORENSIC_STATE);
StateCallback scb = new StateCallback();
assertEquals(STATE_UNKNOWN, scb.mState);
- mForensicService.getBinderService().monitorState(scb);
- mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb.mState);
- }
-
- @Test
- public void testMonitorState_Invisible_TwoMonitors() throws RemoteException {
- StateCallback scb1 = new StateCallback();
- assertEquals(STATE_UNKNOWN, scb1.mState);
- mForensicService.getBinderService().monitorState(scb1);
- mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb1.mState);
-
- StateCallback scb2 = new StateCallback();
- assertEquals(STATE_UNKNOWN, scb2.mState);
- mForensicService.getBinderService().monitorState(scb2);
- mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb2.mState);
+ assertThrows(SecurityException.class,
+ () -> mForensicService.getBinderService().addStateCallback(scb));
}
@Test
- public void testMakeVisible_FromInvisible() throws RemoteException {
+ public void testRemoveStateCallback_NoPermission() {
+ mPermissionEnforcer.revoke(READ_FORENSIC_STATE);
StateCallback scb = new StateCallback();
assertEquals(STATE_UNKNOWN, scb.mState);
- mForensicService.getBinderService().monitorState(scb);
- mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb.mState);
-
- CommandCallback ccb = new CommandCallback();
- mForensicService.getBinderService().makeVisible(ccb);
- mTestLooper.dispatchAll();
- assertEquals(STATE_VISIBLE, scb.mState);
- assertNull(ccb.mErrorCode);
+ assertThrows(SecurityException.class,
+ () -> mForensicService.getBinderService().removeStateCallback(scb));
}
@Test
- public void testMakeVisible_FromInvisible_TwoMonitors() throws RemoteException {
- mForensicService.setState(STATE_INVISIBLE);
- StateCallback scb1 = new StateCallback();
- StateCallback scb2 = new StateCallback();
- mForensicService.getBinderService().monitorState(scb1);
- mForensicService.getBinderService().monitorState(scb2);
- mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb1.mState);
- assertEquals(STATE_INVISIBLE, scb2.mState);
-
- doReturn(true).when(mDataAggregator).initialize();
+ public void testEnable_NoPermission() {
+ mPermissionEnforcer.revoke(MANAGE_FORENSIC_STATE);
CommandCallback ccb = new CommandCallback();
- mForensicService.getBinderService().makeVisible(ccb);
- mTestLooper.dispatchAll();
- assertEquals(STATE_VISIBLE, scb1.mState);
- assertEquals(STATE_VISIBLE, scb2.mState);
- assertNull(ccb.mErrorCode);
+ assertThrows(SecurityException.class,
+ () -> mForensicService.getBinderService().enable(ccb));
}
@Test
- public void testMakeVisible_FromInvisible_TwoMonitors_DataSourceUnavailable()
- throws RemoteException {
- mForensicService.setState(STATE_INVISIBLE);
- StateCallback scb1 = new StateCallback();
- StateCallback scb2 = new StateCallback();
- mForensicService.getBinderService().monitorState(scb1);
- mForensicService.getBinderService().monitorState(scb2);
- mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb1.mState);
- assertEquals(STATE_INVISIBLE, scb2.mState);
-
- doReturn(false).when(mDataAggregator).initialize();
+ public void testDisable_NoPermission() {
+ mPermissionEnforcer.revoke(MANAGE_FORENSIC_STATE);
CommandCallback ccb = new CommandCallback();
- mForensicService.getBinderService().makeVisible(ccb);
- mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb1.mState);
- assertEquals(STATE_INVISIBLE, scb2.mState);
- assertNotNull(ccb.mErrorCode);
- assertEquals(ERROR_DATA_SOURCE_UNAVAILABLE, ccb.mErrorCode.intValue());
+ assertThrows(SecurityException.class,
+ () -> mForensicService.getBinderService().disable(ccb));
}
@Test
- public void testMakeVisible_FromVisible_TwoMonitors() throws RemoteException {
- mForensicService.setState(STATE_VISIBLE);
- StateCallback scb1 = new StateCallback();
- StateCallback scb2 = new StateCallback();
- mForensicService.getBinderService().monitorState(scb1);
- mForensicService.getBinderService().monitorState(scb2);
- mTestLooper.dispatchAll();
- assertEquals(STATE_VISIBLE, scb1.mState);
- assertEquals(STATE_VISIBLE, scb2.mState);
-
- CommandCallback ccb = new CommandCallback();
- mForensicService.getBinderService().makeVisible(ccb);
+ public void testAddStateCallback_Disabled() throws RemoteException {
+ StateCallback scb = new StateCallback();
+ assertEquals(STATE_UNKNOWN, scb.mState);
+ mForensicService.getBinderService().addStateCallback(scb);
mTestLooper.dispatchAll();
- assertEquals(STATE_VISIBLE, scb1.mState);
- assertEquals(STATE_VISIBLE, scb2.mState);
- assertNull(ccb.mErrorCode);
+ assertEquals(STATE_DISABLED, scb.mState);
}
@Test
- public void testMakeVisible_FromEnabled_TwoMonitors() throws RemoteException {
- mForensicService.setState(STATE_ENABLED);
+ public void testAddStateCallback_Disabled_TwoStateCallbacks() throws RemoteException {
StateCallback scb1 = new StateCallback();
- StateCallback scb2 = new StateCallback();
- mForensicService.getBinderService().monitorState(scb1);
- mForensicService.getBinderService().monitorState(scb2);
- mTestLooper.dispatchAll();
- assertEquals(STATE_ENABLED, scb1.mState);
- assertEquals(STATE_ENABLED, scb2.mState);
-
- CommandCallback ccb = new CommandCallback();
- mForensicService.getBinderService().makeVisible(ccb);
+ assertEquals(STATE_UNKNOWN, scb1.mState);
+ mForensicService.getBinderService().addStateCallback(scb1);
mTestLooper.dispatchAll();
- assertEquals(STATE_ENABLED, scb1.mState);
- assertEquals(STATE_ENABLED, scb2.mState);
- assertNotNull(ccb.mErrorCode);
- assertEquals(ERROR_INVALID_STATE_TRANSITION, ccb.mErrorCode.intValue());
- }
+ assertEquals(STATE_DISABLED, scb1.mState);
- @Test
- public void testMakeInvisible_FromInvisible_TwoMonitors() throws RemoteException {
- mForensicService.setState(STATE_INVISIBLE);
- StateCallback scb1 = new StateCallback();
StateCallback scb2 = new StateCallback();
- mForensicService.getBinderService().monitorState(scb1);
- mForensicService.getBinderService().monitorState(scb2);
- mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb1.mState);
- assertEquals(STATE_INVISIBLE, scb2.mState);
-
- CommandCallback ccb = new CommandCallback();
- mForensicService.getBinderService().makeInvisible(ccb);
- mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb1.mState);
- assertEquals(STATE_INVISIBLE, scb2.mState);
- assertNull(ccb.mErrorCode);
- }
-
- @Test
- public void testMakeInvisible_FromVisible_TwoMonitors() throws RemoteException {
- mForensicService.setState(STATE_VISIBLE);
- StateCallback scb1 = new StateCallback();
- StateCallback scb2 = new StateCallback();
- mForensicService.getBinderService().monitorState(scb1);
- mForensicService.getBinderService().monitorState(scb2);
- mTestLooper.dispatchAll();
- assertEquals(STATE_VISIBLE, scb1.mState);
- assertEquals(STATE_VISIBLE, scb2.mState);
-
- CommandCallback ccb = new CommandCallback();
- mForensicService.getBinderService().makeInvisible(ccb);
+ assertEquals(STATE_UNKNOWN, scb2.mState);
+ mForensicService.getBinderService().addStateCallback(scb2);
mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb1.mState);
- assertEquals(STATE_INVISIBLE, scb2.mState);
- assertNull(ccb.mErrorCode);
+ assertEquals(STATE_DISABLED, scb2.mState);
}
@Test
- public void testMakeInvisible_FromEnabled_TwoMonitors() throws RemoteException {
- mForensicService.setState(STATE_ENABLED);
+ public void testRemoveStateCallback() throws RemoteException {
+ mForensicService.setState(STATE_DISABLED);
StateCallback scb1 = new StateCallback();
StateCallback scb2 = new StateCallback();
- mForensicService.getBinderService().monitorState(scb1);
- mForensicService.getBinderService().monitorState(scb2);
+ mForensicService.getBinderService().addStateCallback(scb1);
+ mForensicService.getBinderService().addStateCallback(scb2);
mTestLooper.dispatchAll();
- assertEquals(STATE_ENABLED, scb1.mState);
- assertEquals(STATE_ENABLED, scb2.mState);
+ assertEquals(STATE_DISABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
- CommandCallback ccb = new CommandCallback();
- mForensicService.getBinderService().makeInvisible(ccb);
- mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb1.mState);
- assertEquals(STATE_INVISIBLE, scb2.mState);
- assertNull(ccb.mErrorCode);
- }
+ doReturn(true).when(mDataAggregator).initialize();
+ doReturn(true).when(mForensicEventTransportConnection).initialize();
-
- @Test
- public void testEnable_FromInvisible_TwoMonitors() throws RemoteException {
- mForensicService.setState(STATE_INVISIBLE);
- StateCallback scb1 = new StateCallback();
- StateCallback scb2 = new StateCallback();
- mForensicService.getBinderService().monitorState(scb1);
- mForensicService.getBinderService().monitorState(scb2);
- mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb1.mState);
- assertEquals(STATE_INVISIBLE, scb2.mState);
+ mForensicService.getBinderService().removeStateCallback(scb2);
CommandCallback ccb = new CommandCallback();
mForensicService.getBinderService().enable(ccb);
mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb1.mState);
- assertEquals(STATE_INVISIBLE, scb2.mState);
- assertNotNull(ccb.mErrorCode);
- assertEquals(ERROR_INVALID_STATE_TRANSITION, ccb.mErrorCode.intValue());
+ assertEquals(STATE_ENABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
+ assertNull(ccb.mErrorCode);
}
@Test
- public void testEnable_FromVisible_TwoMonitors() throws RemoteException {
- mForensicService.setState(STATE_VISIBLE);
+ public void testEnable_FromDisabled_TwoStateCallbacks() throws RemoteException {
+ mForensicService.setState(STATE_DISABLED);
StateCallback scb1 = new StateCallback();
StateCallback scb2 = new StateCallback();
- mForensicService.getBinderService().monitorState(scb1);
- mForensicService.getBinderService().monitorState(scb2);
+ mForensicService.getBinderService().addStateCallback(scb1);
+ mForensicService.getBinderService().addStateCallback(scb2);
mTestLooper.dispatchAll();
- assertEquals(STATE_VISIBLE, scb1.mState);
- assertEquals(STATE_VISIBLE, scb2.mState);
+ assertEquals(STATE_DISABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
- doReturn(true).when(mBackupTransportConnection).initialize();
+ doReturn(true).when(mForensicEventTransportConnection).initialize();
CommandCallback ccb = new CommandCallback();
mForensicService.getBinderService().enable(ccb);
@@ -310,35 +203,13 @@ public class ForensicServiceTest {
}
@Test
- public void testEnable_FromVisible_TwoMonitors_BackupTransportUnavailable()
+ public void testEnable_FromEnabled_TwoStateCallbacks()
throws RemoteException {
- mForensicService.setState(STATE_VISIBLE);
- StateCallback scb1 = new StateCallback();
- StateCallback scb2 = new StateCallback();
- mForensicService.getBinderService().monitorState(scb1);
- mForensicService.getBinderService().monitorState(scb2);
- mTestLooper.dispatchAll();
- assertEquals(STATE_VISIBLE, scb1.mState);
- assertEquals(STATE_VISIBLE, scb2.mState);
-
- doReturn(false).when(mBackupTransportConnection).initialize();
-
- CommandCallback ccb = new CommandCallback();
- mForensicService.getBinderService().enable(ccb);
- mTestLooper.dispatchAll();
- assertEquals(STATE_VISIBLE, scb1.mState);
- assertEquals(STATE_VISIBLE, scb2.mState);
- assertNotNull(ccb.mErrorCode);
- assertEquals(ERROR_BACKUP_TRANSPORT_UNAVAILABLE, ccb.mErrorCode.intValue());
- }
-
- @Test
- public void testEnable_FromEnabled_TwoMonitors() throws RemoteException {
mForensicService.setState(STATE_ENABLED);
StateCallback scb1 = new StateCallback();
StateCallback scb2 = new StateCallback();
- mForensicService.getBinderService().monitorState(scb1);
- mForensicService.getBinderService().monitorState(scb2);
+ mForensicService.getBinderService().addStateCallback(scb1);
+ mForensicService.getBinderService().addStateCallback(scb2);
mTestLooper.dispatchAll();
assertEquals(STATE_ENABLED, scb1.mState);
assertEquals(STATE_ENABLED, scb2.mState);
@@ -346,62 +217,44 @@ public class ForensicServiceTest {
CommandCallback ccb = new CommandCallback();
mForensicService.getBinderService().enable(ccb);
mTestLooper.dispatchAll();
+
assertEquals(STATE_ENABLED, scb1.mState);
assertEquals(STATE_ENABLED, scb2.mState);
assertNull(ccb.mErrorCode);
}
@Test
- public void testDisable_FromInvisible_TwoMonitors() throws RemoteException {
- mForensicService.setState(STATE_INVISIBLE);
+ public void testDisable_FromDisabled_TwoStateCallbacks() throws RemoteException {
+ mForensicService.setState(STATE_DISABLED);
StateCallback scb1 = new StateCallback();
StateCallback scb2 = new StateCallback();
- mForensicService.getBinderService().monitorState(scb1);
- mForensicService.getBinderService().monitorState(scb2);
+ mForensicService.getBinderService().addStateCallback(scb1);
+ mForensicService.getBinderService().addStateCallback(scb2);
mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb1.mState);
- assertEquals(STATE_INVISIBLE, scb2.mState);
+ assertEquals(STATE_DISABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
CommandCallback ccb = new CommandCallback();
mForensicService.getBinderService().disable(ccb);
mTestLooper.dispatchAll();
- assertEquals(STATE_INVISIBLE, scb1.mState);
- assertEquals(STATE_INVISIBLE, scb2.mState);
- assertNotNull(ccb.mErrorCode);
- assertEquals(ERROR_INVALID_STATE_TRANSITION, ccb.mErrorCode.intValue());
- }
- @Test
- public void testDisable_FromVisible_TwoMonitors() throws RemoteException {
- mForensicService.setState(STATE_VISIBLE);
- StateCallback scb1 = new StateCallback();
- StateCallback scb2 = new StateCallback();
- mForensicService.getBinderService().monitorState(scb1);
- mForensicService.getBinderService().monitorState(scb2);
- mTestLooper.dispatchAll();
- assertEquals(STATE_VISIBLE, scb1.mState);
- assertEquals(STATE_VISIBLE, scb2.mState);
-
- CommandCallback ccb = new CommandCallback();
- mForensicService.getBinderService().disable(ccb);
- mTestLooper.dispatchAll();
- assertEquals(STATE_VISIBLE, scb1.mState);
- assertEquals(STATE_VISIBLE, scb2.mState);
+ assertEquals(STATE_DISABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
assertNull(ccb.mErrorCode);
}
@Test
- public void testDisable_FromEnabled_TwoMonitors() throws RemoteException {
+ public void testDisable_FromEnabled_TwoStateCallbacks() throws RemoteException {
mForensicService.setState(STATE_ENABLED);
StateCallback scb1 = new StateCallback();
StateCallback scb2 = new StateCallback();
- mForensicService.getBinderService().monitorState(scb1);
- mForensicService.getBinderService().monitorState(scb2);
+ mForensicService.getBinderService().addStateCallback(scb1);
+ mForensicService.getBinderService().addStateCallback(scb2);
mTestLooper.dispatchAll();
assertEquals(STATE_ENABLED, scb1.mState);
assertEquals(STATE_ENABLED, scb2.mState);
- doNothing().when(mBackupTransportConnection).release();
+ doNothing().when(mForensicEventTransportConnection).release();
ServiceThread mockThread = spy(ServiceThread.class);
mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
@@ -412,11 +265,35 @@ public class ForensicServiceTest {
mTestLooperOfDataAggregator.dispatchAll();
// TODO: We can verify the data sources once we implement them.
verify(mockThread, times(1)).quitSafely();
- assertEquals(STATE_VISIBLE, scb1.mState);
- assertEquals(STATE_VISIBLE, scb2.mState);
+ assertEquals(STATE_DISABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
assertNull(ccb.mErrorCode);
}
+ @Ignore("Enable once the ForensicEventTransportConnection is ready")
+ @Test
+ public void testEnable_FromDisable_TwoStateCallbacks_TransportUnavailable()
+ throws RemoteException {
+ mForensicService.setState(STATE_DISABLED);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().addStateCallback(scb1);
+ mForensicService.getBinderService().addStateCallback(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_DISABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
+
+ doReturn(false).when(mForensicEventTransportConnection).initialize();
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().enable(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_DISABLED, scb1.mState);
+ assertEquals(STATE_DISABLED, scb2.mState);
+ assertNotNull(ccb.mErrorCode);
+ assertEquals(ERROR_TRANSPORT_UNAVAILABLE, ccb.mErrorCode.intValue());
+ }
+
@Test
public void testDataAggregator_AddBatchData() {
mForensicService.setState(STATE_ENABLED);
@@ -441,14 +318,14 @@ public class ForensicServiceTest {
events.add(eventOne);
events.add(eventTwo);
- doReturn(true).when(mBackupTransportConnection).addData(any());
+ doReturn(true).when(mForensicEventTransportConnection).addData(any());
mDataAggregator.addBatchData(events);
mTestLooperOfDataAggregator.dispatchAll();
mTestLooper.dispatchAll();
ArgumentCaptor<List<ForensicEvent>> captor = ArgumentCaptor.forClass(List.class);
- verify(mBackupTransportConnection).addData(captor.capture());
+ verify(mForensicEventTransportConnection).addData(captor.capture());
List<ForensicEvent> receivedEvents = captor.getValue();
assertEquals(receivedEvents.size(), 2);
@@ -476,6 +353,10 @@ public class ForensicServiceTest {
return mContext;
}
+ @Override
+ public PermissionEnforcer getPermissionEnforcer() {
+ return mPermissionEnforcer;
+ }
@Override
public Looper getLooper() {
@@ -483,9 +364,9 @@ public class ForensicServiceTest {
}
@Override
- public BackupTransportConnection getBackupTransportConnection() {
- mBackupTransportConnection = spy(new BackupTransportConnection(mContext));
- return mBackupTransportConnection;
+ public ForensicEventTransportConnection getForensicEventransportConnection() {
+ mForensicEventTransportConnection = spy(new ForensicEventTransportConnection(mContext));
+ return mForensicEventTransportConnection;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index b81bf3c6e712..f6f831f41f83 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -21,6 +21,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE;
import static com.android.server.biometrics.sensors.LockoutTracker.LOCKOUT_NONE;
@@ -354,6 +355,21 @@ public class PreAuthInfoTest {
assertThat(preAuthInfo.getIsMandatoryBiometricsAuthentication()).isTrue();
}
+ @Test
+ public void prioritizeStrengthErrorBeforeCameraUnavailableError() throws Exception {
+ final BiometricSensor sensor = getFaceSensorWithStrength(
+ BiometricManager.Authenticators.BIOMETRIC_WEAK);
+ final PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setNegativeButtonText(TEST_PACKAGE_NAME);
+ final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor), USER_ID , promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
+
+ assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
+ }
+
private BiometricSensor getFingerprintSensor() {
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT,
TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG,
@@ -372,9 +388,10 @@ public class PreAuthInfoTest {
return sensor;
}
- private BiometricSensor getFaceSensor() {
+ private BiometricSensor getFaceSensorWithStrength(
+ @BiometricManager.Authenticators.Types int sensorStrength) {
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
- BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+ sensorStrength, mFaceAuthenticator) {
@Override
boolean confirmationAlwaysRequired(int userId) {
return false;
@@ -388,4 +405,8 @@ public class PreAuthInfoTest {
return sensor;
}
+
+ private BiometricSensor getFaceSensor() {
+ return getFaceSensorWithStrength(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 698bda335f83..4c381eb4429e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -24,6 +24,7 @@ import android.app.PendingIntent;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DevicePolicyManagerLiteInternal;
import android.app.backup.IBackupManager;
+import android.app.supervision.SupervisionManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.content.Intent;
@@ -488,6 +489,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi
public Context createContextAsUser(UserHandle user) {
return context;
}
+
+ @Override
+ SupervisionManagerInternal getSupervisionManager() {
+ return services.supervisionManagerInternal;
+ }
}
static class TransferOwnershipMetadataManagerMockInjector extends
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index cb4269a205e4..cf5dc4bec71c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -109,6 +109,7 @@ import android.app.admin.PasswordMetrics;
import android.app.admin.PreferentialNetworkServiceConfig;
import android.app.admin.SystemUpdatePolicy;
import android.app.admin.WifiSsidPolicy;
+import android.app.admin.flags.Flags;
import android.app.role.RoleManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -134,6 +135,10 @@ import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.security.KeyChain;
@@ -165,6 +170,7 @@ import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.internal.util.collections.Sets;
@@ -207,6 +213,9 @@ public class DevicePolicyManagerTest extends DpmTestBase {
public static final String INVALID_CALLING_IDENTITY_MSG = "Calling identity is not authorized";
public static final String ONGOING_CALL_MSG = "ongoing call on the device";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
// TODO replace all instances of this with explicit {@link #mServiceContext}.
@Deprecated
private DpmMockContext mContext;
@@ -4425,6 +4434,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/359188869")
public void testSetAutoTimeEnabledWithPOOfOrganizationOwnedDevice() throws Exception {
setupProfileOwner();
configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
@@ -4902,6 +4912,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
public void testSecondaryLockscreen_profileOwner() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
@@ -4930,6 +4941,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
public void testSecondaryLockscreen_deviceOwner() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
@@ -4948,6 +4960,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
public void testSecondaryLockscreen_nonOwner() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
@@ -4964,6 +4977,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
public void testSecondaryLockscreen_nonSupervisionApp() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
@@ -4996,6 +5010,51 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
+ public void testIsSecondaryLockscreenEnabled() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+
+ verifyIsSecondaryLockscreenEnabled(false);
+ verifyIsSecondaryLockscreenEnabled(true);
+ }
+
+ private void verifyIsSecondaryLockscreenEnabled(boolean expected) throws Exception {
+ reset(getServices().supervisionManagerInternal);
+
+ doReturn(expected).when(getServices().supervisionManagerInternal)
+ .isSupervisionLockscreenEnabledForUser(anyInt());
+
+ final boolean enabled = dpm.isSecondaryLockscreenEnabled(UserHandle.of(CALLER_USER_HANDLE));
+ verify(getServices().supervisionManagerInternal)
+ .isSupervisionLockscreenEnabledForUser(CALLER_USER_HANDLE);
+
+ assertThat(enabled).isEqualTo(expected);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
+ public void testSetSecondaryLockscreenEnabled() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+
+ verifySetSecondaryLockscreenEnabled(false);
+ verifySetSecondaryLockscreenEnabled(true);
+ }
+
+ private void verifySetSecondaryLockscreenEnabled(boolean enabled) throws Exception {
+ reset(getServices().supervisionManagerInternal);
+
+ dpm.setSecondaryLockscreenEnabled(admin1, enabled);
+ verify(getServices().supervisionManagerInternal).setSupervisionLockscreenEnabledForUser(
+ CALLER_USER_HANDLE, enabled, null);
+
+ reset(getServices().supervisionManagerInternal);
+
+ dpm.setSecondaryLockscreenEnabled(enabled, new PersistableBundle());
+ verify(getServices().supervisionManagerInternal).setSupervisionLockscreenEnabledForUser(
+ eq(CALLER_USER_HANDLE), eq(enabled), any(PersistableBundle.class));
+ }
+
+ @Test
public void testIsDeviceManaged() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 2e200a9268f5..3e4448c1dafa 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -35,6 +35,7 @@ import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.app.backup.IBackupManager;
import android.app.role.RoleManager;
+import android.app.supervision.SupervisionManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -77,8 +78,8 @@ import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.server.AlarmManagerInternal;
-import com.android.server.pdb.PersistentDataBlockManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.pkg.PackageState;
@@ -149,6 +150,7 @@ public class MockSystemServices {
public final BuildMock buildMock = new BuildMock();
public final File dataDir;
public final PolicyPathProvider pathProvider;
+ public final SupervisionManagerInternal supervisionManagerInternal;
private final Map<String, PackageState> mTestPackageStates = new ArrayMap<>();
@@ -203,6 +205,7 @@ public class MockSystemServices {
roleManager = realContext.getSystemService(RoleManager.class);
roleManagerForMock = mock(RoleManagerForMock.class);
subscriptionManager = mock(SubscriptionManager.class);
+ supervisionManagerInternal = mock(SupervisionManagerInternal.class);
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(realContext.getPackageManager());
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 73aec6375a03..510c2bcabad0 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -532,6 +532,8 @@ public class MediaProjectionManagerServiceTest {
MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
projection.start(mIMediaProjectionCallback);
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, projection.packageName);
doReturn(true).when(mKeyguardManager).isKeyguardLocked();
MediaProjectionManagerService.BinderService mediaProjectionBinderService =
mService.new BinderService(mContext);
@@ -540,50 +542,6 @@ public class MediaProjectionManagerServiceTest {
verify(mContext, never()).startActivityAsUser(any(), any());
}
- @EnableFlags(android.companion.virtualdevice.flags
- .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
- @Test
- public void testKeyguardLocked_stopsActiveProjection() throws Exception {
- MediaProjectionManagerService service =
- new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
- MediaProjectionManagerService.MediaProjection projection =
- startProjectionPreconditions(service);
- projection.start(mIMediaProjectionCallback);
- projection.notifyVirtualDisplayCreated(10);
-
- assertThat(service.getActiveProjectionInfo()).isNotNull();
-
- doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager)
- .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName);
- service.onKeyguardLockedStateChanged(true);
-
- verify(mMediaProjectionMetricsLogger).logStopped(UID, TARGET_UID_UNKNOWN);
- assertThat(service.getActiveProjectionInfo()).isNull();
- assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue();
- }
-
- @EnableFlags(android.companion.virtualdevice.flags
- .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
- @Test
- public void testKeyguardLocked_packageAllowlisted_doesNotStopActiveProjection()
- throws NameNotFoundException {
- MediaProjectionManagerService service =
- new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
- MediaProjectionManagerService.MediaProjection projection =
- startProjectionPreconditions(service);
- projection.start(mIMediaProjectionCallback);
- projection.notifyVirtualDisplayCreated(10);
-
- assertThat(service.getActiveProjectionInfo()).isNotNull();
-
- doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
- RECORD_SENSITIVE_CONTENT, projection.packageName);
- service.onKeyguardLockedStateChanged(true);
-
- verifyZeroInteractions(mMediaProjectionMetricsLogger);
- assertThat(service.getActiveProjectionInfo()).isNotNull();
- }
-
@Test
public void stop_noActiveProjections_doesNotLog() throws Exception {
MediaProjectionManagerService service =
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java
new file mode 100644
index 000000000000..89d2d2847007
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+
+import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
+import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.app.Instrumentation;
+import android.app.KeyguardManager;
+import android.app.role.RoleManager;
+import android.companion.AssociationRequest;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.projection.MediaProjectionManager;
+import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.telecom.TelecomManager;
+import android.testing.TestableContext;
+import android.util.ArraySet;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Tests for the {@link MediaProjectionStopController} class.
+ * <p>
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:MediaProjectionStopControllerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SuppressLint({"UseCheckPermission", "VisibleForTests", "MissingPermission"})
+public class MediaProjectionStopControllerTest {
+ private static final int UID = 10;
+ private static final String PACKAGE_NAME = "test.package";
+ private final ApplicationInfo mAppInfo = new ApplicationInfo();
+ @Rule
+ public final TestableContext mContext = spy(
+ new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()));
+
+ private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector =
+ new MediaProjectionManagerService.Injector() {
+ @Override
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) {
+ return mMediaProjectionMetricsLogger;
+ }
+ };
+
+ private MediaProjectionManagerService mService;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Mock
+ private ActivityManagerInternal mAmInternal;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private KeyguardManager mKeyguardManager;
+ @Mock
+ private TelecomManager mTelecomManager;
+
+ private AppOpsManager mAppOpsManager;
+ @Mock
+ private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ @Mock
+ private Consumer<Integer> mStopConsumer;
+
+ private MediaProjectionStopController mStopController;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, mAmInternal);
+
+ mAppOpsManager = mockAppOpsManager();
+ mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
+ mContext.addMockSystemService(KeyguardManager.class, mKeyguardManager);
+ mContext.addMockSystemService(TelecomManager.class, mTelecomManager);
+ mContext.setMockPackageManager(mPackageManager);
+
+ mStopController = new MediaProjectionStopController(mContext, mStopConsumer);
+ mService = new MediaProjectionManagerService(mContext,
+ mMediaProjectionMetricsLoggerInjector);
+
+ mAppInfo.targetSdkVersion = 35;
+ }
+
+ private static AppOpsManager mockAppOpsManager() {
+ return mock(AppOpsManager.class, invocationOnMock -> {
+ if (invocationOnMock.getMethod().getName().startsWith("noteOp")) {
+ // Mockito will return 0 for non-stubbed method which corresponds to MODE_ALLOWED
+ // and is not what we want.
+ return AppOpsManager.MODE_IGNORED;
+ }
+ return Answers.RETURNS_DEFAULTS.answer(invocationOnMock);
+ });
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.removeServiceForTest(WindowManagerInternal.class);
+ }
+
+ @Test
+ @EnableFlags(
+ android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ public void testMediaProjectionNotRestricted() throws Exception {
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
+
+ assertThat(mStopController.isStartForbidden(
+ createMediaProjection(PACKAGE_NAME))).isFalse();
+ }
+
+ @Test
+ @EnableFlags(
+ android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ public void testMediaProjectionRestricted() throws Exception {
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ mediaProjection.notifyVirtualDisplayCreated(1);
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+
+ assertThat(mStopController.isStartForbidden(mediaProjection)).isTrue();
+ }
+
+ @Test
+ public void testExemptFromStoppingNullProjection() throws Exception {
+ assertThat(mStopController.isExemptFromStopping(null)).isTrue();
+ }
+
+ @Test
+ public void testExemptFromStoppingInvalidProjection() throws Exception {
+ assertThat(mStopController.isExemptFromStopping(createMediaProjection(null))).isTrue();
+ }
+
+ @Test
+ public void testExemptFromStoppingDisableScreenshareProtections() throws Exception {
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ int value = Settings.Global.getInt(mContext.getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0);
+ try {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 1);
+
+ assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+ } finally {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, value);
+ }
+ }
+
+ @Test
+ public void testExemptFromStoppingHasOpProjectMedia() throws Exception {
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+ .noteOpNoThrow(eq(AppOpsManager.OP_PROJECT_MEDIA),
+ eq(mediaProjection.uid), eq(mediaProjection.packageName),
+ nullable(String.class),
+ nullable(String.class));
+ assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+ }
+
+ @Test
+ public void testExemptFromStoppingHasAppStreamingRole() throws Exception {
+ runWithRole(
+ AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+ () -> {
+ try {
+ MediaProjectionManagerService.MediaProjection mediaProjection =
+ createMediaProjection();
+ doReturn(PackageManager.PERMISSION_DENIED).when(
+ mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ @Test
+ public void testExemptFromStoppingIsBugreportAllowlisted() throws Exception {
+ ArraySet<String> packages = SystemConfig.getInstance().getBugreportWhitelistedPackages();
+ if (packages.isEmpty()) {
+ return;
+ }
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection(
+ packages.valueAt(0));
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+ }
+
+ @Test
+ public void testExemptFromStoppingHasNoDisplay() throws Exception {
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection(
+ PACKAGE_NAME);
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+ }
+
+ @Test
+ public void testExemptFromStoppingHasRecordSensitiveContentPermission() throws Exception {
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+ }
+
+ @Test
+ public void testExemptFromStoppingIsFalse() throws Exception {
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ mediaProjection.notifyVirtualDisplayCreated(1);
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+ assertThat(mStopController.isExemptFromStopping(mediaProjection)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(
+ android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ public void testKeyguardLockedStateChanged_unlocked() {
+ mStopController.onKeyguardLockedStateChanged(false);
+
+ verify(mStopConsumer, never()).accept(anyInt());
+ }
+
+ @Test
+ @EnableFlags(
+ android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ public void testKeyguardLockedStateChanged_locked() {
+ mStopController.onKeyguardLockedStateChanged(true);
+
+ verify(mStopConsumer).accept(MediaProjectionStopController.STOP_REASON_KEYGUARD);
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ public void testCallStateChanged_callStarts() {
+ // Setup call state to false
+ when(mTelecomManager.isInCall()).thenReturn(false);
+ mStopController.callStateChanged();
+
+ clearInvocations(mStopConsumer);
+
+ when(mTelecomManager.isInCall()).thenReturn(true);
+ mStopController.callStateChanged();
+
+ verify(mStopConsumer, never()).accept(anyInt());
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ public void testCallStateChanged_remainsInCall() {
+ // Setup call state to false
+ when(mTelecomManager.isInCall()).thenReturn(true);
+ mStopController.callStateChanged();
+
+ clearInvocations(mStopConsumer);
+
+ when(mTelecomManager.isInCall()).thenReturn(true);
+ mStopController.callStateChanged();
+
+ verify(mStopConsumer, never()).accept(anyInt());
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ public void testCallStateChanged_remainsNoCall() {
+ // Setup call state to false
+ when(mTelecomManager.isInCall()).thenReturn(false);
+ mStopController.callStateChanged();
+
+ clearInvocations(mStopConsumer);
+
+ when(mTelecomManager.isInCall()).thenReturn(false);
+ mStopController.callStateChanged();
+
+ verify(mStopConsumer, never()).accept(anyInt());
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ public void testCallStateChanged_callEnds() {
+ // Setup call state to false
+ when(mTelecomManager.isInCall()).thenReturn(true);
+ mStopController.callStateChanged();
+
+ clearInvocations(mStopConsumer);
+
+ when(mTelecomManager.isInCall()).thenReturn(false);
+ mStopController.callStateChanged();
+
+ verify(mStopConsumer).accept(MediaProjectionStopController.STOP_REASON_CALL_END);
+ }
+
+ private MediaProjectionManagerService.MediaProjection createMediaProjection()
+ throws NameNotFoundException {
+ return createMediaProjection(PACKAGE_NAME);
+ }
+
+ private MediaProjectionManagerService.MediaProjection createMediaProjection(String packageName)
+ throws NameNotFoundException {
+ doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
+ any(ApplicationInfoFlags.class), any(UserHandle.class));
+ doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(Mockito.isNull(),
+ any(ApplicationInfoFlags.class), any(UserHandle.class));
+ return mService.createProjectionInternal(UID, packageName,
+ MediaProjectionManager.TYPE_SCREEN_CAPTURE, false, mContext.getUser(),
+ INVALID_DISPLAY);
+ }
+
+ /**
+ * Run the provided block giving the current context's package the provided role.
+ */
+ @SuppressWarnings("SameParameterValue")
+ private void runWithRole(String role, Runnable block) {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ String packageName = mContext.getPackageName();
+ UserHandle user = instrumentation.getTargetContext().getUser();
+ RoleManager roleManager = Objects.requireNonNull(
+ mContext.getSystemService(RoleManager.class));
+ try {
+ CountDownLatch latch = new CountDownLatch(1);
+ instrumentation.getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.MANAGE_ROLE_HOLDERS,
+ Manifest.permission.BYPASS_ROLE_QUALIFICATION);
+
+ roleManager.setBypassingRoleQualification(true);
+ roleManager.addRoleHolderAsUser(role, packageName,
+ /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
+ mContext.getMainExecutor(), success -> {
+ if (success) {
+ latch.countDown();
+ } else {
+ Assert.fail("Couldn't set role for test (failure) " + role);
+ }
+ });
+ assertWithMessage("Couldn't set role for test (timeout) : " + role)
+ .that(latch.await(1, TimeUnit.SECONDS)).isTrue();
+ block.run();
+
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } finally {
+ roleManager.removeRoleHolderAsUser(role, packageName,
+ /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
+ mContext.getMainExecutor(), (aBool) -> {
+ });
+ roleManager.setBypassingRoleQualification(false);
+ instrumentation.getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
index 79b0623640f6..8290e1cfb9db 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -20,7 +20,7 @@ import android.app.admin.DevicePolicyManagerInternal
import android.content.ComponentName
import android.content.Context
import android.content.pm.UserInfo
-import android.os.Bundle
+import android.os.PersistableBundle
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -139,7 +139,7 @@ class SupervisionServiceTest {
assertThat(userData.supervisionLockScreenEnabled).isFalse()
assertThat(userData.supervisionLockScreenOptions).isNull()
- service.mInternal.setSupervisionLockscreenEnabledForUser(USER_ID, true, Bundle())
+ service.mInternal.setSupervisionLockscreenEnabledForUser(USER_ID, true, PersistableBundle())
userData = service.getUserDataLocked(USER_ID)
assertThat(userData.supervisionLockScreenEnabled).isTrue()
assertThat(userData.supervisionLockScreenOptions).isNotNull()
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index cc0286508cdc..6af65423415b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -84,7 +84,9 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.GroupHelper.CachedSummary;
+import com.android.server.notification.GroupHelper.FullyQualifiedGroupKey;
import com.android.server.notification.GroupHelper.NotificationAttributes;
+import com.android.server.notification.GroupHelper.NotificationSectioner;
import org.junit.Before;
import org.junit.Rule;
@@ -2298,6 +2300,7 @@ public class GroupHelperTest extends UiServiceTestCase {
final String pkg = "package";
final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ final int numNotifications = 2 * AUTOGROUP_AT_COUNT;
int numNotificationChannel1 = 0;
final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1",
"TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT);
@@ -2307,7 +2310,7 @@ public class GroupHelperTest extends UiServiceTestCase {
final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
// Post notifications with different channels that autogroup within the same section
NotificationRecord r;
- for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ for (int i = 0; i < numNotifications; i++) {
if (i % 2 == 0) {
r = getNotificationRecord(pkg, i, String.valueOf(i),
UserHandle.SYSTEM, "testGrp " + i, false, channel1);
@@ -2324,12 +2327,12 @@ public class GroupHelperTest extends UiServiceTestCase {
"TEST_CHANNEL_ID1");
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
eq(expectedGroupKey_alerting), anyInt(), eq(expectedSummaryAttr));
- verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ verify(mCallback, times(numNotifications)).addAutoGroup(anyString(),
eq(expectedGroupKey_alerting), eq(true));
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
- any());
+ verify(mCallback, times(numNotifications - AUTOGROUP_AT_COUNT)).updateAutogroupSummary(
+ anyInt(), anyString(), anyString(), any());
Mockito.reset(mCallback);
// Update channel1's importance
@@ -2375,7 +2378,7 @@ public class GroupHelperTest extends UiServiceTestCase {
final List<NotificationRecord> notificationList = new ArrayList<>();
final String pkg = "package";
final int summaryId = 0;
- final int numChildNotif = 4;
+ final int numChildNotif = 2 * AUTOGROUP_AT_COUNT;
// Create an app-provided group: summary + child notifications
final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1",
@@ -2435,8 +2438,8 @@ public class GroupHelperTest extends UiServiceTestCase {
eq(expectedGroupKey_social), eq(true));
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
- verify(mCallback, times(numChildNotif / 2)).updateAutogroupSummary(anyInt(), anyString(),
- anyString(), any());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
verify(mCallback, times(numChildNotif)).removeAppProvidedSummaryOnClassification(
anyString(), eq(originalAppGroupKey));
}
@@ -2513,9 +2516,10 @@ public class GroupHelperTest extends UiServiceTestCase {
final List<NotificationRecord> notificationList = new ArrayList<>();
final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
final String pkg = "package";
+ final int numChildNotifications = AUTOGROUP_AT_COUNT;
// Post singleton groups, above forced group limit
- for (int i = 0; i < AUTOGROUP_SINGLETONS_AT_COUNT; i++) {
+ for (int i = 0; i < numChildNotifications; i++) {
NotificationRecord summary = getNotificationRecord(pkg, i,
String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true);
notificationList.add(summary);
@@ -2545,13 +2549,13 @@ public class GroupHelperTest extends UiServiceTestCase {
// Check that notifications are forced grouped and app-provided summaries are canceled
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
- verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(),
+ verify(mCallback, times(numChildNotifications)).addAutoGroup(anyString(),
eq(expectedGroupKey_social), eq(true));
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
- verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
any());
- verify(mCallback, times(2)).removeAppProvidedSummaryOnClassification(
+ verify(mCallback, times(numChildNotifications)).removeAppProvidedSummaryOnClassification(
anyString(), anyString());
// Adjust group key and cancel summaries
@@ -2593,14 +2597,16 @@ public class GroupHelperTest extends UiServiceTestCase {
AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
String expectedTriggeringKey = null;
// Post singleton groups, above forced group limit
- for (int i = 0; i < AUTOGROUP_SINGLETONS_AT_COUNT; i++) {
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
NotificationRecord summary = getNotificationRecord(pkg, i,
String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true);
notificationList.add(summary);
NotificationRecord child = getNotificationRecord(pkg, i + 42,
String.valueOf(i + 42), UserHandle.SYSTEM, "testGrp " + i, false);
notificationList.add(child);
- expectedTriggeringKey = child.getKey();
+ if (i == AUTOGROUP_SINGLETONS_AT_COUNT - 1) {
+ expectedTriggeringKey = child.getKey();
+ }
summaryByGroup.put(summary.getGroupKey(), summary);
mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
summary.isCanceled = true; // simulate removing the app summary
@@ -2611,14 +2617,8 @@ public class GroupHelperTest extends UiServiceTestCase {
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg),
eq(expectedTriggeringKey), eq(expectedGroupKey_alerting), anyInt(),
eq(getNotificationAttributes(BASE_FLAGS)));
- verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(),
- eq(expectedGroupKey_alerting), eq(true));
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
- any());
- verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).removeAppProvidedSummary(
- anyString());
assertThat(mGroupHelper.findCanceledSummary(pkg, String.valueOf(0), 0,
UserHandle.SYSTEM.getIdentifier())).isNotNull();
assertThat(mGroupHelper.findCanceledSummary(pkg, String.valueOf(1), 1,
@@ -2645,12 +2645,12 @@ public class GroupHelperTest extends UiServiceTestCase {
// Check that all notifications are moved to the social section group
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
- verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(),
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
eq(expectedGroupKey_social), eq(true));
// Check that the alerting section group is removed
verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
eq(expectedGroupKey_alerting));
- verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).updateAutogroupSummary(anyInt(),
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).updateAutogroupSummary(anyInt(),
anyString(), anyString(), any());
}
@@ -2666,7 +2666,7 @@ public class GroupHelperTest extends UiServiceTestCase {
final String pkg = "package";
final int summaryId = 0;
- final int numChildren = 3;
+ final int numChildren = AUTOGROUP_AT_COUNT;
// Post a regular/valid group: summary + notifications
NotificationRecord summary = getNotificationRecord(pkg, summaryId,
String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
@@ -2706,13 +2706,211 @@ public class GroupHelperTest extends UiServiceTestCase {
eq(true));
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
- verify(mCallback, times(numChildren - 1)).updateAutogroupSummary(anyInt(), anyString(),
- anyString(), any());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
verify(mCallback, times(numChildren)).removeAppProvidedSummaryOnClassification(anyString(),
anyString());
}
@Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+ FLAG_NOTIFICATION_CLASSIFICATION})
+ public void testUnbundleNotification_originalSummaryMissing_autogroupInNewSection() {
+ // Check that unbundled notifications are moved to the original section and aggregated
+ // with existing autogrouped notifications
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+
+ final int summaryId = 0;
+ final int numChildren = AUTOGROUP_AT_COUNT - 1;
+ // Post a regular/valid group: summary + notifications (one less than autogroup limit)
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ final String originalAppGroupKey = summary.getGroupKey();
+ final NotificationChannel originalChannel = summary.getChannel();
+ for (int i = 0; i < numChildren; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp", false);
+ notificationList.add(child);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ }
+
+ // Classify/bundle all child notifications: original group & summary is removed
+ final NotificationChannel socialChannel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_DEFAULT);
+ for (NotificationRecord record: notificationList) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
+ record.updateNotificationChannel(socialChannel);
+ mGroupHelper.onChannelUpdated(record);
+ }
+ }
+
+ // Check that no autogroup summaries were created for the social section
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ verify(mCallback, times(numChildren)).removeAppProvidedSummaryOnClassification(
+ anyString(), eq(originalAppGroupKey));
+
+ // Cancel summary
+ summary.isCanceled = true;
+ summaryByGroup.clear();
+ notificationList.remove(summary);
+
+ // Add 1 ungrouped notification in the original section
+ NotificationRecord ungroupedNotification = getNotificationRecord(pkg, 4242,
+ String.valueOf(4242), UserHandle.SYSTEM);
+ notificationList.add(ungroupedNotification);
+ mGroupHelper.onNotificationPosted(ungroupedNotification, false);
+
+ // Unbundle the bundled notifications => notifications are moved back to the original group
+ // and an aggregate group is created because autogroup limit is reached
+ reset(mCallback);
+ for (NotificationRecord record: notificationList) {
+ if (record.getNotification().isGroupChild()
+ && record.getOriginalGroupKey().contains("testGrp")
+ && NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+ record.getChannel().getId())) {
+ record.updateNotificationChannel(originalChannel);
+ mGroupHelper.onNotificationUnbundled(record, false);
+ }
+ }
+
+ // Check that a new aggregate group is created
+ final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_alerting), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_alerting), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, times(numChildren)).removeAutoGroupSummary(anyInt(), anyString(),
+ anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+ FLAG_NOTIFICATION_CLASSIFICATION})
+ public void testUnbundleNotification_originalSummaryExists() {
+ // Check that unbundled notifications are moved to the original section and original group
+ // when the original summary is still present
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+
+ final int summaryId = 0;
+ final int numChildren = AUTOGROUP_AT_COUNT + 1;
+ // Post a regular/valid group: summary + notifications
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ final String originalAppGroupKey = summary.getGroupKey();
+ final NotificationChannel originalChannel = summary.getChannel();
+ for (int i = 0; i < numChildren; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp", false);
+ notificationList.add(child);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ }
+
+ // Classify/bundle child notifications: all except one, to keep the original group
+ final NotificationChannel socialChannel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_DEFAULT);
+ final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+ final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+ BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ NotificationChannel.SOCIAL_MEDIA_ID);
+ int numChildrenBundled = 0;
+ for (NotificationRecord record: notificationList) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
+ record.updateNotificationChannel(socialChannel);
+ mGroupHelper.onChannelUpdated(record);
+ numChildrenBundled++;
+ if (numChildrenBundled == AUTOGROUP_AT_COUNT) {
+ break;
+ }
+ }
+ }
+
+ // Check that 1 autogroup summaries were created for the social section
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_social), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).removeAppProvidedSummaryOnClassification(
+ anyString(), eq(originalAppGroupKey));
+
+ // Adjust group key and cancel summaries
+ for (NotificationRecord record: notificationList) {
+ if (record.getNotification().isGroupSummary()) {
+ record.isCanceled = true;
+ } else if (record.getOriginalGroupKey().contains("testGrp")
+ && NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+ record.getChannel().getId())) {
+ record.setOverrideGroupKey(expectedGroupKey_social);
+ }
+ }
+
+ // Add 1 ungrouped notification in the original section
+ NotificationRecord ungroupedNotification = getNotificationRecord(pkg, 4242,
+ String.valueOf(4242), UserHandle.SYSTEM);
+ notificationList.add(ungroupedNotification);
+ mGroupHelper.onNotificationPosted(ungroupedNotification, false);
+
+ // Unbundle the bundled notifications => social section summary is destroyed
+ // and notifications are moved back to the original group
+ reset(mCallback);
+ for (NotificationRecord record: notificationList) {
+ if (record.getNotification().isGroupChild()
+ && record.getOriginalGroupKey().contains("testGrp")
+ && NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+ record.getChannel().getId())) {
+ record.updateNotificationChannel(originalChannel);
+ mGroupHelper.onNotificationUnbundled(record, true);
+ }
+ }
+
+ // Check that the autogroup summary for the social section was removed
+ // and that no new autogroup summaries were created
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey_social));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).updateAutogroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey_social), any());
+
+ for (NotificationRecord record: notificationList) {
+ if (record.getNotification().isGroupChild()
+ && record.getOriginalGroupKey().contains("testGrp")) {
+ assertThat(record.getSbn().getOverrideGroupKey()).isNull();
+ }
+ }
+ }
+
+ @Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testMoveAggregateGroups_updateChannel_groupsUngrouped() {
final String pkg = "package";
@@ -3059,6 +3257,120 @@ public class GroupHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
@DisableFlags(FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS)
+ public void testNonGroupableChildren_singletonGroups_disableConversations() {
+ // Check that singleton groups with children that are not groupable, is not grouped
+ // Even though the group summary is a regular (alerting) notification, the children are
+ // conversations => the group should not be forced grouped.
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+
+ // Trigger notification, ungrouped
+ final int triggerId = 1;
+ NotificationRecord triggerNotification = getNotificationRecord(pkg, triggerId,
+ String.valueOf(triggerId), UserHandle.SYSTEM);
+ notificationList.add(triggerNotification);
+ final NotificationSectioner triggerSection = GroupHelper.getSection(triggerNotification);
+ final FullyQualifiedGroupKey triggerFullAggregateGroupKey = new FullyQualifiedGroupKey(
+ triggerNotification.getUserId(), triggerNotification.getSbn().getPackageName(),
+ triggerSection);
+
+ // Add singleton group with alerting child
+ final String groupName_valid = "testGrp_valid";
+ final int summaryId_valid = 0;
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId_valid,
+ String.valueOf(summaryId_valid), UserHandle.SYSTEM, groupName_valid, true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ final String groupKey_valid = summary.getGroupKey();
+ NotificationRecord child = getNotificationRecord(pkg, summaryId_valid + 42,
+ String.valueOf(summaryId_valid + 42), UserHandle.SYSTEM, groupName_valid, false);
+ notificationList.add(child);
+
+ // Add singleton group with conversation child
+ final String groupName_invalid = "testGrp_invalid";
+ final int summaryId_invalid = 100;
+ summary = getNotificationRecord(pkg, summaryId_invalid,
+ String.valueOf(summaryId_invalid), UserHandle.SYSTEM, groupName_invalid, true);
+ notificationList.add(summary);
+ final String groupKey_invalid = summary.getGroupKey();
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ child = getNotificationRecord(pkg, summaryId_invalid + 42,
+ String.valueOf(summaryId_invalid + 42), UserHandle.SYSTEM, groupName_invalid,
+ false);
+ child = spy(child);
+ when(child.isConversation()).thenReturn(true);
+ notificationList.add(child);
+
+ // Check that the invalid group will not be force grouped
+ final ArrayMap<String, NotificationRecord> sparseGroups = mGroupHelper.getSparseGroups(
+ triggerFullAggregateGroupKey, notificationList, summaryByGroup, triggerSection);
+ assertThat(sparseGroups).containsKey(groupKey_valid);
+ assertThat(sparseGroups).doesNotContainKey(groupKey_invalid);
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS})
+ public void testNonGroupableChildren_singletonGroups_enableConversations() {
+ // Check that singleton groups with children that are not groupable, is not grouped
+ // Conversations are groupable (FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS is enabled)
+ // The invalid group is the alerting notifications: because the triggering notifications'
+ // section is Conversations, so the alerting group should be skipped.
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+
+ // Trigger notification, ungrouped conversation
+ final int triggerId = 1;
+ NotificationRecord triggerNotification = getNotificationRecord(pkg, triggerId,
+ String.valueOf(triggerId), UserHandle.SYSTEM);
+ triggerNotification = spy(triggerNotification);
+ when(triggerNotification.isConversation()).thenReturn(true);
+ notificationList.add(triggerNotification);
+ final NotificationSectioner triggerSection = GroupHelper.getSection(triggerNotification);
+ final FullyQualifiedGroupKey triggerFullAggregateGroupKey = new FullyQualifiedGroupKey(
+ triggerNotification.getUserId(), triggerNotification.getSbn().getPackageName(),
+ triggerSection);
+
+ // Add singleton group with conversation child
+ final String groupName_valid = "testGrp_valid";
+ final int summaryId_valid = 0;
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId_valid,
+ String.valueOf(summaryId_valid), UserHandle.SYSTEM, groupName_valid, true);
+ summary = spy(summary);
+ when(summary.isConversation()).thenReturn(true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ final String groupKey_valid = summary.getGroupKey();
+ NotificationRecord child = getNotificationRecord(pkg, summaryId_valid + 42,
+ String.valueOf(summaryId_valid + 42), UserHandle.SYSTEM, groupName_valid, false);
+ child = spy(child);
+ when(child.isConversation()).thenReturn(true);
+ notificationList.add(child);
+
+ // Add singleton group with non-conversation child
+ final String groupName_invalid = "testGrp_invalid";
+ final int summaryId_invalid = 100;
+ summary = getNotificationRecord(pkg, summaryId_invalid,
+ String.valueOf(summaryId_invalid), UserHandle.SYSTEM, groupName_invalid, true);
+ notificationList.add(summary);
+ final String groupKey_invalid = summary.getGroupKey();
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ child = getNotificationRecord(pkg, summaryId_invalid + 42,
+ String.valueOf(summaryId_invalid + 42), UserHandle.SYSTEM, groupName_invalid,
+ false);
+ notificationList.add(child);
+
+ // Check that the invalid group will not be force grouped
+ final ArrayMap<String, NotificationRecord> sparseGroups = mGroupHelper.getSparseGroups(
+ triggerFullAggregateGroupKey, notificationList, summaryByGroup, triggerSection);
+ assertThat(sparseGroups).containsKey(groupKey_valid);
+ assertThat(sparseGroups).doesNotContainKey(groupKey_invalid);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS)
public void testNonGroupableNotifications() {
// Check that there is no valid section for: conversations, calls, foreground services
NotificationRecord notification_conversation = mock(NotificationRecord.class);
diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml
index 197b36623fff..787f4e85c012 100644
--- a/services/tests/wmtests/res/xml/bookmarks.xml
+++ b/services/tests/wmtests/res/xml/bookmarks.xml
@@ -13,60 +13,70 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<bookmarks>
+<bookmarks xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<!-- the key combinations for the following shortcuts must be in sync
with the key combinations sent by the test in ModifierShortcutTests.java -->
<bookmark
role="android.app.role.BROWSER"
- shortcut="b" />
+ androidprv:keycode="KEYCODE_B"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CONTACTS"
- shortcut="c" />
+ androidprv:keycode="KEYCODE_C"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_EMAIL"
- shortcut="e" />
+ androidprv:keycode="KEYCODE_E"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CALENDAR"
- shortcut="k" />
+ androidprv:keycode="KEYCODE_K"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_MAPS"
- shortcut="m" />
+ androidprv:keycode="KEYCODE_M"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_MUSIC"
- shortcut="p" />
+ androidprv:keycode="KEYCODE_P"
+ androidprv:modifierState="META" />
<bookmark
role="android.app.role.SMS"
- shortcut="s" />
+ androidprv:keycode="KEYCODE_S"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CALCULATOR"
- shortcut="u" />
+ androidprv:keycode="KEYCODE_U"
+ androidprv:modifierState="META" />
<bookmark
role="android.app.role.BROWSER"
- shortcut="b"
- shift="true" />
+ androidprv:keycode="KEYCODE_B"
+ androidprv:modifierState="META|SHIFT" />
<bookmark
category="android.intent.category.APP_CONTACTS"
- shortcut="c"
- shift="true" />
+ androidprv:keycode="KEYCODE_C"
+ androidprv:modifierState="META|SHIFT" />
<bookmark
package="com.test"
class="com.test.BookmarkTest"
- shortcut="j"
- shift="true" />
+ androidprv:keycode="KEYCODE_J"
+ androidprv:modifierState="META|SHIFT" />
<!-- The following shortcuts will not be invoked by tests but are here to
provide test coverage of parsing the different types of shortcut. -->
<bookmark
package="com.test"
class="com.test.BookmarkTest"
- shortcut="j" />
+ androidprv:keycode="KEYCODE_J"
+ androidprv:modifierState="META" />
<bookmark
package="com.test2"
class="com.test.BookmarkTest"
- shortcut="d" />
+ androidprv:keycode="KEYCODE_D"
+ androidprv:modifierState="META" />
<!-- It's intended that this package/class will NOT resolve so we test the resolution
@@ -74,6 +84,7 @@
<bookmark
package="com.test3"
class="com.test.BookmarkTest"
- shortcut="f" />
+ androidprv:keycode="KEYCODE_F"
+ androidprv:modifierState="META" />
</bookmarks>
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
index 0575d98b65ec..82a5add407f4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -116,6 +116,7 @@ public class ModifierShortcutManagerTests {
mModifierShortcutManager = new ModifierShortcutManager(
mContext, mHandler, UserHandle.SYSTEM);
+ mModifierShortcutManager.onSystemReady();
}
@Test
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 a51ce9951ab4..bc03c233b459 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -88,6 +88,7 @@ import android.service.dreams.DreamManagerInternal;
import android.telecom.TelecomManager;
import android.view.Display;
import android.view.InputEvent;
+import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.autofill.AutofillManagerInternal;
@@ -270,11 +271,15 @@ class TestPhoneWindowManager {
// Return mocked services: LocalServices.getService
mMockitoSession = mockitoSession()
.mockStatic(LocalServices.class, spyStubOnly)
+ .mockStatic(KeyCharacterMap.class)
.strictness(Strictness.LENIENT)
.startMocking();
mPhoneWindowManager = spy(new PhoneWindowManager());
+ KeyCharacterMap virtualKcm = mContext.getSystemService(InputManager.class)
+ .getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD).getKeyCharacterMap();
+ doReturn(virtualKcm).when(() -> KeyCharacterMap.load(anyInt()));
doReturn(mWindowManagerInternal).when(
() -> LocalServices.getService(eq(WindowManagerInternal.class)));
doReturn(mActivityManagerInternal).when(
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 42e31de295d6..817c368745d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -82,6 +82,7 @@ import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
@@ -122,6 +123,8 @@ import java.util.function.BiConsumer;
* Build/Install/Run:
* atest WmTests:WindowOrganizerTests
*/
+
+// TODO revert parts of this set to set the flag to test the behavior
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
@@ -1299,6 +1302,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
@Test
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
public void testEnterPipParams() {
final StubOrganizer o = new StubOrganizer();
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o);
@@ -1314,6 +1318,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
@Test
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
public void testChangePipParams() {
class ChangeSavingOrganizer extends StubOrganizer {
RunningTaskInfo mChangedInfo;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index b737d35c1534..50e0e181cd68 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -71,6 +71,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -84,12 +85,14 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
+import android.content.ContentResolver;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Build;
import android.os.IBinder;
import android.os.InputConfig;
import android.os.RemoteException;
@@ -97,6 +100,7 @@ import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.view.Gravity;
@@ -1557,6 +1561,57 @@ public class WindowStateTests extends WindowTestsBase {
}
@Test
+ public void testIsSecureLocked_flagSecureSet() {
+ WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
+ 1 /* ownerId */);
+ window.mAttrs.flags |= WindowManager.LayoutParams.FLAG_SECURE;
+
+ assertTrue(window.isSecureLocked());
+ }
+
+ @Test
+ public void testIsSecureLocked_flagSecureNotSet() {
+ WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
+ 1 /* ownerId */);
+
+ assertFalse(window.isSecureLocked());
+ }
+
+ @Test
+ public void testIsSecureLocked_disableSecureWindows() {
+ assumeTrue(Build.IS_DEBUGGABLE);
+
+ WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
+ 1 /* ownerId */);
+ window.mAttrs.flags |= WindowManager.LayoutParams.FLAG_SECURE;
+ ContentResolver cr = useFakeSettingsProvider();
+
+ // isSecureLocked should return false when DISABLE_SECURE_WINDOWS is set to 1
+ Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, "1");
+ mWm.mSettingsObserver.onChange(false /* selfChange */,
+ Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+ assertFalse(window.isSecureLocked());
+
+ // isSecureLocked should return true if DISABLE_SECURE_WINDOWS is set to 0.
+ Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, "0");
+ mWm.mSettingsObserver.onChange(false /* selfChange */,
+ Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+ assertTrue(window.isSecureLocked());
+
+ // Disable secure windows again.
+ Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, "1");
+ mWm.mSettingsObserver.onChange(false /* selfChange */,
+ Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+ assertFalse(window.isSecureLocked());
+
+ // isSecureLocked should return true if DISABLE_SECURE_WINDOWS is deleted.
+ Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, null);
+ mWm.mSettingsObserver.onChange(false /* selfChange */,
+ Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+ assertTrue(window.isSecureLocked());
+ }
+
+ @Test
@RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
public void testIsSecureLocked_sensitiveContentProtectionManagerEnabled() {
String testPackage = "test";
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 9f5e6d18dc03..2c998c4217ac 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.helpers
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.graphics.Insets
import android.graphics.Rect
@@ -74,13 +75,28 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
.waitForAndVerify()
}
+ /** Launch an app and ensure it's moved to Desktop if it has not. */
+ fun enterDesktopMode(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH),
+ ) {
+ innerHelper.launchViaIntent(wmHelper)
+ if (!isInDesktopWindowingMode(wmHelper)) {
+ enterDesktopModeWithDrag(
+ wmHelper = wmHelper,
+ device = device,
+ motionEventHelper = motionEventHelper
+ )
+ }
+ }
+
/** Move an app to Desktop by dragging the app handle at the top. */
- fun enterDesktopWithDrag(
+ fun enterDesktopModeWithDrag(
wmHelper: WindowManagerStateHelper,
device: UiDevice,
motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH)
) {
- innerHelper.launchViaIntent(wmHelper)
dragToDesktop(
wmHelper = wmHelper,
device = device,
@@ -150,6 +166,25 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
.waitForAndVerify()
}
+ private fun getHeaderEmptyView(caption: UiObject2?): UiObject2 {
+ return caption
+ ?.children
+ ?.find { it.resourceName.endsWith(HEADER_EMPTY_VIEW) }
+ ?: error("Unable to find resource $HEADER_EMPTY_VIEW\n")
+ }
+
+ /** Click on an existing window's header to bring it to the front. */
+ fun bringToFront(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+ val caption = getCaptionForTheApp(wmHelper, device)
+ val openHeaderView = getHeaderEmptyView(caption)
+ openHeaderView.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withTopVisibleApp(innerHelper)
+ .waitForAndVerify()
+ }
+
/** Open maximize menu and click snap resize button on the app header for the given app. */
fun snapResizeDesktopApp(
wmHelper: WindowManagerStateHelper,
@@ -415,6 +450,10 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
return metricInsets.getInsetsIgnoringVisibility(typeMask)
}
+ // Requirement of DesktopWindowingMode is having a minimum of 1 app in WINDOWING_MODE_FREEFORM.
+ private fun isInDesktopWindowingMode(wmHelper: WindowManagerStateHelper) =
+ wmHelper.getWindow(innerHelper)?.windowingMode == WINDOWING_MODE_FREEFORM
+
private companion object {
val TIMEOUT: Duration = Duration.ofSeconds(3)
const val SNAP_RESIZE_DRAG_INSET: Int = 5 // inset to avoid dragging to display edge
@@ -427,6 +466,7 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button"
const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button"
const val MINIMIZE_BUTTON_VIEW: String = "minimize_window"
+ const val HEADER_EMPTY_VIEW: String = "caption_handle"
val caption: BySelector
get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
}
diff --git a/tests/Input/res/xml/bookmarks.xml b/tests/Input/res/xml/bookmarks.xml
index ba3f1871cdec..a4c898d8159a 100644
--- a/tests/Input/res/xml/bookmarks.xml
+++ b/tests/Input/res/xml/bookmarks.xml
@@ -14,47 +14,55 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<bookmarks>
+<bookmarks xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<!-- the key combinations for the following shortcuts must be in sync
with the key combinations sent by the test in KeyGestureControllerTests.java -->
<bookmark
role="android.app.role.BROWSER"
- shortcut="b" />
+ androidprv:keycode="KEYCODE_B"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CONTACTS"
- shortcut="c" />
+ androidprv:keycode="KEYCODE_C"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_EMAIL"
- shortcut="e" />
+ androidprv:keycode="KEYCODE_E"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CALENDAR"
- shortcut="k" />
+ androidprv:keycode="KEYCODE_K"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_MAPS"
- shortcut="m" />
+ androidprv:keycode="KEYCODE_M"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_MUSIC"
- shortcut="p" />
+ androidprv:keycode="KEYCODE_P"
+ androidprv:modifierState="META" />
<bookmark
role="android.app.role.SMS"
- shortcut="s" />
+ androidprv:keycode="KEYCODE_S"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CALCULATOR"
- shortcut="u" />
+ androidprv:keycode="KEYCODE_U"
+ androidprv:modifierState="META" />
<bookmark
role="android.app.role.BROWSER"
- shortcut="b"
- shift="true" />
+ androidprv:keycode="KEYCODE_B"
+ androidprv:modifierState="META|SHIFT" />
<bookmark
category="android.intent.category.APP_CONTACTS"
- shortcut="c"
+ androidprv:keycode="KEYCODE_C"
shift="true" />
<bookmark
package="com.test"
class="com.test.BookmarkTest"
- shortcut="j"
+ androidprv:keycode="KEYCODE_J"
shift="true" />
</bookmarks> \ No newline at end of file
diff --git a/tests/Input/res/xml/bookmarks_legacy.xml b/tests/Input/res/xml/bookmarks_legacy.xml
new file mode 100644
index 000000000000..8bacf490ad9e
--- /dev/null
+++ b/tests/Input/res/xml/bookmarks_legacy.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<bookmarks>
+ <!-- The key combinations for the following shortcuts are legacy way defining bookmarks and we
+ should prefer new way of defining bookmarks as shown in {@link bookmarks.xml} -->
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b" />
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c" />
+ <bookmark
+ category="android.intent.category.APP_EMAIL"
+ shortcut="e" />
+ <bookmark
+ category="android.intent.category.APP_CALENDAR"
+ shortcut="k" />
+ <bookmark
+ category="android.intent.category.APP_MAPS"
+ shortcut="m" />
+ <bookmark
+ category="android.intent.category.APP_MUSIC"
+ shortcut="p" />
+ <bookmark
+ role="android.app.role.SMS"
+ shortcut="s" />
+ <bookmark
+ category="android.intent.category.APP_CALCULATOR"
+ shortcut="u" />
+
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b"
+ shift="true" />
+
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c"
+ shift="true" />
+
+ <bookmark
+ package="com.test"
+ class="com.test.BookmarkTest"
+ shortcut="j"
+ shift="true" />
+</bookmarks> \ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 09a686ca2c3f..d1f866843be6 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -115,13 +115,11 @@ class KeyGestureControllerTests {
private lateinit var iInputManager: IInputManager
@Mock
- private lateinit var resources: Resources
-
- @Mock
private lateinit var packageManager: PackageManager
private var currentPid = 0
private lateinit var context: Context
+ private lateinit var resources: Resources
private lateinit var keyGestureController: KeyGestureController
private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
private lateinit var testLooper: TestLooper
@@ -130,6 +128,7 @@ class KeyGestureControllerTests {
@Before
fun setup() {
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ resources = Mockito.spy(context.resources)
setupInputDevices()
setupBehaviors()
testLooper = TestLooper()
@@ -146,10 +145,6 @@ class KeyGestureControllerTests {
private fun setupBehaviors() {
Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1")
Mockito.`when`(resources.getBoolean(R.bool.config_enableScreenshotChord)).thenReturn(true)
- val testBookmarks: XmlResourceParser = context.resources.getXml(
- com.android.test.input.R.xml.bookmarks
- )
- Mockito.`when`(resources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks)
Mockito.`when`(context.resources).thenReturn(resources)
Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
.thenReturn(true)
@@ -158,6 +153,11 @@ class KeyGestureControllerTests {
Mockito.`when`(context.packageManager).thenReturn(packageManager)
}
+ private fun setupBookmarks(bookmarkRes: Int) {
+ val testBookmarks: XmlResourceParser = context.resources.getXml(bookmarkRes)
+ Mockito.`when`(resources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks)
+ }
+
private fun setupInputDevices() {
val correctIm = context.getSystemService(InputManager::class.java)!!
val virtualDevice = correctIm.getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD)!!
@@ -604,45 +604,6 @@ class KeyGestureControllerTests {
AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
),
TestData(
- "META + SHIFT + B -> Launch Default Browser",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_SHIFT_LEFT,
- KeyEvent.KEYCODE_B
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
- intArrayOf(KeyEvent.KEYCODE_B),
- KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
- AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
- ),
- TestData(
- "META + SHIFT + C -> Launch Default Contacts",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_SHIFT_LEFT,
- KeyEvent.KEYCODE_C
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
- intArrayOf(KeyEvent.KEYCODE_C),
- KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
- AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
- ),
- TestData(
- "META + SHIFT + J -> Launch Target Activity",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_SHIFT_LEFT,
- KeyEvent.KEYCODE_J
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
- intArrayOf(KeyEvent.KEYCODE_J),
- KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
- AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
- ),
- TestData(
"META + CTRL + DEL -> Trigger Bug Report",
intArrayOf(
KeyEvent.KEYCODE_META_LEFT,
@@ -866,6 +827,139 @@ class KeyGestureControllerTests {
}
@Keep
+ private fun bookmarkArguments(): Array<TestData> {
+ return arrayOf(
+ TestData(
+ "META + B -> Launch Default Browser",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_B),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_B),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+ ),
+ TestData(
+ "META + C -> Launch Default Contacts",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_C),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_C),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+ ),
+ TestData(
+ "META + E -> Launch Default Email",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_E),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_E),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL)
+ ),
+ TestData(
+ "META + K -> Launch Default Calendar",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_K),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_K),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR)
+ ),
+ TestData(
+ "META + M -> Launch Default Maps",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_M),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_M),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MAPS)
+ ),
+ TestData(
+ "META + P -> Launch Default Music",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_P),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_P),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC)
+ ),
+ TestData(
+ "META + S -> Launch Default SMS",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_S),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_S),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_SMS)
+ ),
+ TestData(
+ "META + U -> Launch Default Calculator",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_U),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_U),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
+ ),
+ TestData(
+ "META + SHIFT + B -> Launch Default Browser",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_B
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_B),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+ ),
+ TestData(
+ "META + SHIFT + C -> Launch Default Contacts",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_C
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_C),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+ ),
+ TestData(
+ "META + SHIFT + J -> Launch Target Activity",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_J
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_J),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
+ )
+ )
+ }
+
+ @Test
+ @Parameters(method = "bookmarkArguments")
+ fun testBookmarks(test: TestData) {
+ setupBookmarks(com.android.test.input.R.xml.bookmarks)
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
+ }
+
+ @Test
+ @Parameters(method = "bookmarkArguments")
+ fun testBookmarksLegacy(test: TestData) {
+ setupBookmarks(com.android.test.input.R.xml.bookmarks_legacy)
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
+ }
+
+ @Keep
private fun systemKeysTestArguments(): Array<TestData> {
return arrayOf(
TestData(
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
index 2692e12c57ed..44641f7a1e12 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
@@ -24,6 +24,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -57,6 +58,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
import perfetto.protos.Protolog;
import perfetto.protos.ProtologCommon;
@@ -858,6 +860,39 @@ public class ProcessedPerfettoProtoLogImplTest {
.isEqualTo("This message should also be logged 567");
}
+ @Test
+ public void enablesLogGroupAfterLoadingConfig() {
+ sProtoLog.stopLoggingToLogcat(
+ new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+
+ doAnswer((Answer<Void>) invocation -> {
+ // logToLogcat is still false before we laod the viewer config
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+ return null;
+ }).when(sReader).unloadViewerConfig(any(), any());
+
+ sProtoLog.startLoggingToLogcat(
+ new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isTrue();
+ }
+
+ @Test
+ public void disablesLogGroupBeforeUnloadingConfig() {
+ sProtoLog.startLoggingToLogcat(
+ new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isTrue();
+
+ doAnswer((Answer<Void>) invocation -> {
+ // Already set logToLogcat to false by the time we unload the config
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+ return null;
+ }).when(sReader).unloadViewerConfig(any(), any());
+ sProtoLog.stopLoggingToLogcat(
+ new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+ }
+
private enum TestProtoLogGroup implements IProtoLogGroup {
TEST_GROUP(true, true, false, "TEST_TAG");
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 1bef5f8b17f6..1d4adc4a57d8 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -207,14 +207,13 @@ static ResourceTable::CollisionResult MergeConfigValue(
Value* dst_value = dst_config_value->value.get();
Value* src_value = src_config_value->value.get();
- CollisionResult collision_result;
- if (overlay) {
- collision_result =
- ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool);
- } else {
- collision_result =
- ResourceTable::ResolveFlagCollision(dst_value->GetFlagStatus(), src_value->GetFlagStatus());
- if (collision_result == CollisionResult::kConflict) {
+ CollisionResult collision_result =
+ ResourceTable::ResolveFlagCollision(dst_value->GetFlagStatus(), src_value->GetFlagStatus());
+ if (collision_result == CollisionResult::kConflict) {
+ if (overlay) {
+ collision_result =
+ ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool);
+ } else {
collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value);
}
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index f68ae2c7e249..74a907f8ae22 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -309,7 +309,7 @@ public class SharedConnectivityManager {
*/
@TestApi
@NonNull
- @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api")
+ @SuppressLint("UnflaggedApi") // Exempt: Test API for already shipped feature
public BroadcastReceiver getBroadcastReceiver() {
return mBroadcastReceiver;
}