summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp62
-rw-r--r--apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java38
-rw-r--r--apex/jobscheduler/framework/aconfig/job.aconfig1
-rw-r--r--apex/jobscheduler/service/Android.bp1
-rw-r--r--apex/jobscheduler/service/aconfig/device_idle.aconfig10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java3
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java11
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java91
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java4
-rw-r--r--apex/jobscheduler/service/jni/Android.bp4
-rw-r--r--api/coverage/tools/Android.bp21
-rw-r--r--api/coverage/tools/ExtractFlaggedApis.kt11
-rw-r--r--api/coverage/tools/ExtractFlaggedApisTest.kt160
-rw-r--r--cmds/locksettings/TEST_MAPPING5
-rw-r--r--core/api/current.txt3
-rw-r--r--core/api/test-current.txt19
-rw-r--r--core/java/android/adaptiveauth/flags.aconfig1
-rw-r--r--core/java/android/app/ActivityManager.java9
-rw-r--r--core/java/android/app/ActivityOptions.java13
-rw-r--r--core/java/android/app/ActivityThread.java30
-rw-r--r--core/java/android/app/ClientTransactionHandler.java6
-rw-r--r--core/java/android/app/ConfigurationController.java19
-rw-r--r--core/java/android/app/ContextImpl.java32
-rw-r--r--core/java/android/app/IActivityManager.aidl9
-rw-r--r--core/java/android/app/INotificationManager.aidl4
-rw-r--r--core/java/android/app/Notification.java130
-rw-r--r--core/java/android/app/WallpaperManager.java14
-rw-r--r--core/java/android/app/activity_manager.aconfig1
-rw-r--r--core/java/android/app/admin/AccountTypePolicyKey.java2
-rw-r--r--core/java/android/app/admin/BundlePolicyValue.java2
-rw-r--r--core/java/android/app/admin/ComponentNamePolicyValue.java2
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java63
-rw-r--r--core/java/android/app/admin/EnforcingAdmin.java16
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl6
-rw-r--r--core/java/android/app/admin/LockTaskPolicy.java2
-rw-r--r--core/java/android/app/admin/PackagePermissionPolicyKey.java2
-rw-r--r--core/java/android/app/admin/PackagePolicyKey.java2
-rw-r--r--core/java/android/app/admin/Provisioning_OWNERS2
-rw-r--r--core/java/android/app/admin/StringPolicyValue.java2
-rw-r--r--core/java/android/app/admin/StringSetPolicyValue.java2
-rw-r--r--core/java/android/app/admin/UserRestrictionPolicyKey.java2
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig22
-rw-r--r--core/java/android/app/background_install_control_manager.aconfig1
-rw-r--r--core/java/android/app/contextualsearch/flags.aconfig1
-rw-r--r--core/java/android/app/grammatical_inflection_manager.aconfig1
-rw-r--r--core/java/android/app/multitasking.aconfig1
-rw-r--r--core/java/android/app/network-policy.aconfig1
-rw-r--r--core/java/android/app/notification.aconfig8
-rw-r--r--core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig1
-rw-r--r--core/java/android/app/pinner-client.aconfig1
-rw-r--r--core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java7
-rw-r--r--core/java/android/app/servertransaction/ActivityRelaunchItem.java7
-rw-r--r--core/java/android/app/servertransaction/ClientTransactionItem.java10
-rw-r--r--core/java/android/app/servertransaction/ClientTransactionListenerController.java102
-rw-r--r--core/java/android/app/servertransaction/ConfigurationChangeItem.java8
-rw-r--r--core/java/android/app/servertransaction/LaunchActivityItem.java9
-rw-r--r--core/java/android/app/servertransaction/MoveToDisplayItem.java7
-rw-r--r--core/java/android/app/servertransaction/TransactionExecutor.java57
-rw-r--r--core/java/android/app/servertransaction/WindowContextInfoChangeItem.java7
-rw-r--r--core/java/android/app/servertransaction/WindowStateResizeItem.java25
-rw-r--r--core/java/android/app/smartspace/flags.aconfig1
-rw-r--r--core/java/android/app/ui_mode_manager.aconfig1
-rw-r--r--core/java/android/app/usage/flags.aconfig1
-rw-r--r--core/java/android/app/wearable/flags.aconfig1
-rw-r--r--core/java/android/appwidget/flags.aconfig8
-rw-r--r--core/java/android/companion/ObservingDevicePresenceRequest.java6
-rw-r--r--core/java/android/companion/flags.aconfig1
-rw-r--r--core/java/android/companion/virtual/flags.aconfig12
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig1
-rw-r--r--core/java/android/content/Context.java32
-rw-r--r--core/java/android/content/flags/flags.aconfig1
-rw-r--r--core/java/android/content/pm/UserProperties.java2
-rw-r--r--core/java/android/content/pm/flags.aconfig1
-rw-r--r--core/java/android/content/pm/multiuser.aconfig1
-rw-r--r--core/java/android/content/res/flags.aconfig1
-rw-r--r--core/java/android/credentials/CredentialManager.java4
-rw-r--r--core/java/android/credentials/flags.aconfig1
-rw-r--r--core/java/android/database/sqlite/flags.aconfig1
-rw-r--r--core/java/android/hardware/biometrics/flags.aconfig1
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java9
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java4
-rw-r--r--core/java/android/hardware/devicestate/feature/flags.aconfig1
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java8
-rw-r--r--core/java/android/hardware/flags/overlayproperties_flags.aconfig1
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl25
-rw-r--r--core/java/android/hardware/input/InputManager.java125
-rw-r--r--core/java/android/hardware/input/InputManagerGlobal.java25
-rw-r--r--core/java/android/hardware/input/InputSettings.java16
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig1
-rw-r--r--core/java/android/hardware/radio/flags.aconfig1
-rw-r--r--core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig1
-rw-r--r--core/java/android/hardware/usb/flags/usb_framework_flags.aconfig1
-rw-r--r--core/java/android/net/flags.aconfig1
-rw-r--r--core/java/android/net/thread/flags.aconfig1
-rw-r--r--core/java/android/net/vcn/flags.aconfig1
-rw-r--r--core/java/android/os/MessageQueue.java3
-rw-r--r--core/java/android/os/OomKillRecord.java20
-rw-r--r--core/java/android/os/RecoverySystem.java4
-rw-r--r--core/java/android/os/UserManager.java2
-rw-r--r--core/java/android/os/flags.aconfig1
-rw-r--r--core/java/android/os/vibrator/flags.aconfig1
-rw-r--r--core/java/android/permission/PermissionManager.java10
-rw-r--r--core/java/android/permission/flags.aconfig12
-rw-r--r--core/java/android/provider/Settings.java21
-rw-r--r--core/java/android/provider/flags.aconfig1
-rw-r--r--core/java/android/security/flags.aconfig1
-rw-r--r--core/java/android/security/responsible_apis_flags.aconfig1
-rw-r--r--core/java/android/service/appprediction/flags/flags.aconfig1
-rw-r--r--core/java/android/service/chooser/flags.aconfig1
-rw-r--r--core/java/android/service/controls/flags/flags.aconfig1
-rw-r--r--core/java/android/service/credentials/CredentialProviderInfoFactory.java2
-rw-r--r--core/java/android/service/dreams/flags.aconfig3
-rw-r--r--core/java/android/service/notification/flags.aconfig1
-rw-r--r--core/java/android/service/voice/flags/flags.aconfig1
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java16
-rw-r--r--core/java/android/speech/flags/speech_flags.aconfig1
-rw-r--r--core/java/android/text/flags/flags.aconfig1
-rw-r--r--core/java/android/tracing/flags.aconfig1
-rw-r--r--core/java/android/tracing/perfetto/DataSource.java32
-rw-r--r--core/java/android/tracing/perfetto/TraceFunction.java9
-rw-r--r--core/java/android/tracing/perfetto/TracingContext.java27
-rw-r--r--core/java/android/util/PackageUtils.java5
-rw-r--r--core/java/android/view/HandwritingInitiator.java5
-rw-r--r--core/java/android/view/HapticFeedbackConstants.java22
-rw-r--r--core/java/android/view/ImeInsetsSourceConsumer.java4
-rw-r--r--core/java/android/view/MotionEvent.java7
-rw-r--r--core/java/android/view/ViewRootImpl.java12
-rw-r--r--core/java/android/view/WindowManager.java3
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig11
-rw-r--r--core/java/android/view/contentcapture/flags/content_capture_flags.aconfig1
-rw-r--r--core/java/android/view/contentprotection/flags/content_protection_flags.aconfig1
-rw-r--r--core/java/android/view/flags/refresh_rate_flags.aconfig1
-rw-r--r--core/java/android/view/flags/scroll_feedback_flags.aconfig1
-rw-r--r--core/java/android/view/flags/view_flags.aconfig1
-rw-r--r--core/java/android/view/flags/window_insets.aconfig1
-rw-r--r--core/java/android/view/inputmethod/flags.aconfig1
-rw-r--r--core/java/android/webkit/flags.aconfig1
-rw-r--r--core/java/android/widget/Toast.java117
-rw-r--r--core/java/android/widget/flags/differential_motion_fling_flags.aconfig1
-rw-r--r--core/java/android/widget/flags/notification_widget_flags.aconfig13
-rw-r--r--core/java/android/window/TransitionInfo.java5
-rw-r--r--core/java/android/window/WindowTokenClient.java15
-rw-r--r--core/java/android/window/flags/accessibility.aconfig1
-rw-r--r--core/java/android/window/flags/large_screen_experiences_app_compat.aconfig1
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig15
-rw-r--r--core/java/android/window/flags/responsible_apis.aconfig1
-rw-r--r--core/java/android/window/flags/wallpaper_manager.aconfig11
-rw-r--r--core/java/android/window/flags/window_surfaces.aconfig1
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig20
-rw-r--r--core/java/android/window/flags/windowing_sdk.aconfig1
-rw-r--r--core/java/com/android/internal/accessibility/AccessibilityShortcutController.java36
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java19
-rw-r--r--core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java34
-rw-r--r--core/java/com/android/internal/os/LongArrayMultiStateCounter.java15
-rw-r--r--core/java/com/android/internal/os/PowerProfile.java42
-rw-r--r--core/java/com/android/internal/os/PowerStats.java172
-rw-r--r--core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig1
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java21
-rw-r--r--core/java/com/android/internal/power/ModemPowerProfile.java91
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java142
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java4
-rw-r--r--core/java/com/android/internal/widget/MessagingGroup.java5
-rw-r--r--core/jni/android_os_MessageQueue.cpp2
-rw-r--r--core/jni/android_tracing_PerfettoDataSource.cpp241
-rw-r--r--core/jni/android_tracing_PerfettoDataSource.h10
-rw-r--r--core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp10
-rw-r--r--core/res/AndroidManifest.xml14
-rw-r--r--core/res/OWNERS11
-rw-r--r--core/res/res/drawable/pointer_spot_anchor_vector.xml4
-rw-r--r--core/res/res/drawable/pointer_spot_hover_vector.xml3
-rw-r--r--core/res/res/drawable/pointer_spot_touch_vector.xml3
-rw-r--r--core/res/res/values-mcc404/config.xml2
-rw-r--r--core/res/res/values-mcc405/config.xml2
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/config_battery_stats.xml3
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/res/res/xml/sms_short_codes.xml39
-rw-r--r--core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java7
-rw-r--r--core/tests/coretests/Android.bp5
-rw-r--r--core/tests/coretests/res/xml/power_profile_test.xml14
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java78
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java57
-rw-r--r--core/tests/coretests/src/android/view/MotionEventTest.java63
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java98
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java42
-rw-r--r--core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java15
-rw-r--r--core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java79
-rw-r--r--core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java50
-rw-r--r--core/tests/mockingcoretests/Android.bp1
-rw-r--r--core/tests/mockingcoretests/src/android/widget/OWNERS1
-rw-r--r--core/tests/mockingcoretests/src/android/widget/ToastTest.java143
-rw-r--r--core/tests/overlaytests/device_non_system/Android.bp (renamed from packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp)25
-rw-r--r--core/tests/overlaytests/device_non_system/AndroidManifest.xml29
-rw-r--r--core/tests/overlaytests/device_non_system/AndroidTest.xml45
-rw-r--r--core/tests/overlaytests/device_non_system/res/layout/layout.xml28
-rw-r--r--core/tests/overlaytests/device_non_system/res/values/overlayable.xml24
-rw-r--r--core/tests/overlaytests/device_non_system/res/values/strings.xml3
-rw-r--r--core/tests/overlaytests/device_non_system/src/com/android/overlaytest/OverlayTest.java47
-rw-r--r--core/tests/overlaytests/device_non_system/test-apps/AppOverlay/Android.bp (renamed from packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp)16
-rw-r--r--core/tests/overlaytests/device_non_system/test-apps/AppOverlay/AndroidManifest.xml26
-rw-r--r--core/tests/overlaytests/device_non_system/test-apps/AppOverlay/res/xml/overlays.xml19
-rw-r--r--data/etc/core.protolog.pbbin54086 -> 54168 bytes
-rw-r--r--data/etc/services.core.protolog.json24
-rw-r--r--data/keyboards/Generic.kl2
-rw-r--r--keystore/java/android/security/AndroidKeyStoreMaintenance.java20
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt56
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml5
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java311
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java719
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java1081
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt79
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt58
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java26
-rw-r--r--libs/hwui/Android.bp2
-rw-r--r--libs/hwui/platform/host/renderthread/RenderThread.cpp2
-rw-r--r--libs/hwui/renderstate/RenderState.h6
-rw-r--r--libs/hwui/renderthread/EglManager.cpp19
-rw-r--r--location/java/android/location/flags/location.aconfig1
-rw-r--r--media/java/android/media/MediaRouter2.java115
-rw-r--r--media/java/android/media/flags/editing.aconfig1
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig1
-rw-r--r--media/java/android/media/flags/projection.aconfig1
-rw-r--r--media/java/android/media/session/ISessionManager.aidl3
-rw-r--r--media/java/android/media/tv/flags/media_tv.aconfig1
-rw-r--r--media/jni/playback_flags.aconfig1
-rw-r--r--native/android/performance_hint.cpp25
-rw-r--r--nfc/Android.bp1
-rw-r--r--nfc/TEST_MAPPING3
-rw-r--r--nfc/java/android/nfc/flags.aconfig1
-rw-r--r--packages/CompanionDeviceManager/res/values/styles.xml2
-rw-r--r--packages/CrashRecovery/aconfig/flags.aconfig1
-rw-r--r--packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java6
-rw-r--r--packages/CrashRecovery/services/java/com/android/server/RescueParty.java4
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java115
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java103
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/FileUtils.java128
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java46
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java188
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java118
-rw-r--r--packages/CredentialManager/res/values/strings.xml7
-rw-r--r--packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt32
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt73
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt41
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/BiometricFlowType.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt)8
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt234
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/BiometricPromptState.kt26
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt77
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt95
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt22
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt14
-rw-r--r--packages/InputDevices/res/raw/keyboard_layout_german.kcm49
-rw-r--r--packages/InputDevices/res/raw/keyboard_layout_thai_kedmanee.kcm321
-rw-r--r--packages/InputDevices/res/values/strings.xml3
-rw-r--r--packages/InputDevices/res/xml/keyboard_layouts.xml7
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java3
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt8
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt4
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/NonScrollablePagerPageProvider.kt (renamed from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt)35
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt46
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/ScrollablePagerPageProvider.kt74
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt19
-rw-r--r--packages/SettingsLib/Spa/gradle/libs.versions.toml2
-rw-r--r--packages/SettingsLib/Spa/gradle/wrapper/gradle-8.7-bin.zip (renamed from packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip)bin132788867 -> 134184980 bytes
-rw-r--r--packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.pngbin56192 -> 56885 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.pngbin8935 -> 9126 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.pngbin49209 -> 49759 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.pngbin8935 -> 9126 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.pngbin66109 -> 66586 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.pngbin29970 -> 29473 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.pngbin5559 -> 5427 bytes
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle.kts2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt13
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt7
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt2
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt4
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt2
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt6
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java157
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java122
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig14
-rw-r--r--packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml2
-rw-r--r--packages/SettingsLib/res/values/dimens.xml1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java34
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java311
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java28
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java31
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java4
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java50
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java42
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java34
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java4
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java2
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java19
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml4
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml5
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java7
-rw-r--r--packages/SystemUI/aconfig/Android.bp1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig27
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt4
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt53
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt39
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt67
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt48
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt497
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt28
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt64
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt94
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt76
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt16
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt39
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt110
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt19
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt84
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt54
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt107
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt41
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt33
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt55
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt20
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt46
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt61
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt24
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt101
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt232
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt33
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt89
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt33
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt95
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt32
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt70
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt70
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt)8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt)31
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt53
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt71
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt146
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt153
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt316
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt358
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartableTest.kt93
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt2
-rw-r--r--packages/SystemUI/res/drawable/ic_bugreport.xml32
-rw-r--r--packages/SystemUI/res/drawable/ic_noise_aware.xml26
-rw-r--r--packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml5
-rw-r--r--packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml1
-rw-r--r--packages/SystemUI/res/layout/record_issue_dialog.xml41
-rw-r--r--packages/SystemUI/res/values-land-television/dimens.xml5
-rw-r--r--packages/SystemUI/res/values/colors_tv.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/res/values/styles.xml2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt25
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java11
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractor.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepository.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt)12
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedInputEventBuffer.java (renamed from packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java)100
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeUi.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt86
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/RefactorKeyguardDismissIntent.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt117
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaLoadingModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BasicAppIconLoader.kt (renamed from packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt)24
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt89
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt137
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt113
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt210
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java286
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt285
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt175
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt165
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt (renamed from packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml)24
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt91
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt639
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt96
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt639
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/UiEventLoggerStartableModule.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartable.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/model/AncSlices.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/domain/VolumePanelStartable.kt (renamed from packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml)20
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java81
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java90
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java90
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt)4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt)38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepositoryTest.kt)4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt)4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt)4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt)4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedMotionEventBufferTest.java)52
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt70
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt316
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt152
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt473
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt)19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt62
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt104
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt85
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt214
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt117
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt77
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt80
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java81
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/dock/DockManagerFake.java)2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFakeKosmos.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/dock/DockManagerFakeKosmos.kt)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/BrokenWithSceneContainer.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/DisableSceneContainer.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerFlagParameterization.kt60
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt20
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt18
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt39
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt37
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt (renamed from packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml)20
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt (renamed from packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml)21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt32
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/truth/TruthUtils.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt5
-rw-r--r--packages/overlays/Android.bp3
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml27
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml62
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml30
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp30
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml27
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml62
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml30
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml22
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml27
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml62
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml30
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml22
-rw-r--r--ravenwood/TEST_MAPPING11
-rw-r--r--ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp2
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java47
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java39
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java43
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java (renamed from ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest.java)2
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java (renamed from ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java)2
-rw-r--r--ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java (renamed from ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java)4
-rw-r--r--ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java6
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java26
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java35
-rw-r--r--ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java (renamed from ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java)2
-rw-r--r--ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java (renamed from ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java)2
-rw-r--r--ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java (renamed from ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java)2
-rw-r--r--ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java14
-rw-r--r--ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java (renamed from ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java)2
-rw-r--r--ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java (renamed from ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java)2
-rw-r--r--ravenwood/test-authors.md3
-rw-r--r--services/Android.bp1
-rw-r--r--services/accessibility/accessibility.aconfig7
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java31
-rw-r--r--services/autofill/bugfixes.aconfig1
-rw-r--r--services/autofill/features.aconfig1
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java88
-rw-r--r--services/companion/java/com/android/server/companion/utils/PermissionsUtils.java6
-rw-r--r--services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java31
-rw-r--r--services/core/Android.bp4
-rw-r--r--services/core/java/com/android/server/SensitiveContentProtectionManagerService.java106
-rw-r--r--services/core/java/com/android/server/SystemServiceManager.java8
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java8
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java132
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java5
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java49
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java40
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java13
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java2
-rw-r--r--services/core/java/com/android/server/am/UserController.java93
-rw-r--r--services/core/java/com/android/server/app/flags.aconfig1
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java6
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java21
-rw-r--r--services/core/java/com/android/server/backup/SystemBackupAgent.java10
-rw-r--r--services/core/java/com/android/server/backup/SystemGrammaticalGenderBackupHelper.java66
-rw-r--r--services/core/java/com/android/server/biometrics/Utils.java20
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceService.java6
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java8
-rw-r--r--services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java4
-rw-r--r--services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java3
-rw-r--r--services/core/java/com/android/server/broadcastradio/RadioEventLogger.java (renamed from services/core/java/com/android/server/broadcastradio/aidl/RadioLogger.java)24
-rw-r--r--services/core/java/com/android/server/broadcastradio/aidl/AnnouncementAggregator.java6
-rw-r--r--services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java3
-rw-r--r--services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java8
-rw-r--r--services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java8
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java3
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java46
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java4
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java4
-rw-r--r--services/core/java/com/android/server/connectivity/Android.bp10
-rw-r--r--services/core/java/com/android/server/connectivity/flags.aconfig8
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java2
-rw-r--r--services/core/java/com/android/server/display/DisplayBrightnessState.java38
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java14
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java13
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java46
-rw-r--r--services/core/java/com/android/server/display/ExternalDisplayPolicy.java2
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java14
-rw-r--r--services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java74
-rw-r--r--services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java63
-rw-r--r--services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java74
-rw-r--r--services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java30
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java80
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java430
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java5
-rw-r--r--services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java54
-rw-r--r--services/core/java/com/android/server/display/mode/DisplayModeDirector.java83
-rw-r--r--services/core/java/com/android/server/flags/compaction.aconfig1
-rw-r--r--services/core/java/com/android/server/flags/pinner.aconfig1
-rw-r--r--services/core/java/com/android/server/flags/services.aconfig1
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java36
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java11
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java16
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java48
-rw-r--r--services/core/java/com/android/server/input/KeyboardLayoutManager.java430
-rw-r--r--services/core/java/com/android/server/input/PersistentDataStore.java185
-rw-r--r--services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java464
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodBindingController.java9
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java246
-rw-r--r--services/core/java/com/android/server/inputmethod/ZeroJankProxy.java162
-rw-r--r--services/core/java/com/android/server/locksettings/TEST_MAPPING6
-rw-r--r--services/core/java/com/android/server/media/MediaSession2Record.java5
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java144
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecordImpl.java6
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java174
-rw-r--r--services/core/java/com/android/server/media/MediaShellCommand.java6
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java3
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java48
-rw-r--r--services/core/java/com/android/server/notification/RankingHelper.java12
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerService.java134
-rw-r--r--services/core/java/com/android/server/os/BugreportManagerServiceImpl.java10
-rw-r--r--services/core/java/com/android/server/os/TEST_MAPPING4
-rw-r--r--services/core/java/com/android/server/pm/NoFilteringResolver.java7
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java3
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java13
-rw-r--r--services/core/java/com/android/server/pm/PackageSessionVerifier.java4
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java7
-rw-r--r--services/core/java/com/android/server/pm/UserRestrictionsUtils.java3
-rw-r--r--services/core/java/com/android/server/pm/UserTypeFactory.java9
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java13
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java6
-rw-r--r--services/core/java/com/android/server/power/stats/AggregatedPowerStats.java16
-rw-r--r--services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java55
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java44
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java301
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java23
-rw-r--r--services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java338
-rw-r--r--services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java178
-rw-r--r--services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java (renamed from services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java)17
-rw-r--r--services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java86
-rw-r--r--services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java392
-rw-r--r--services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java255
-rw-r--r--services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java434
-rw-r--r--services/core/java/com/android/server/power/stats/MultiStateStats.java8
-rw-r--r--services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java96
-rw-r--r--services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java198
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsAggregator.java4
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsCollector.java281
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsExporter.java19
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsLayout.java243
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsProcessor.java (renamed from services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java)8
-rw-r--r--services/core/java/com/android/server/powerstats/PowerStatsService.java30
-rw-r--r--services/core/java/com/android/server/recoverysystem/RecoverySystemService.java45
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java36
-rw-r--r--services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java26
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java5
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperCropper.java121
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java15
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java72
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java59
-rw-r--r--services/core/java/com/android/server/webkit/flags.aconfig1
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java19
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java49
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java12
-rw-r--r--services/core/java/com/android/server/wm/DisplayAreaPolicy.java11
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java35
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java28
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java11
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java12
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java4
-rw-r--r--services/core/java/com/android/server/wm/Session.java4
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java13
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java3
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java11
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java63
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java32
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java20
-rw-r--r--services/core/jni/com_android_server_am_OomConnection.cpp12
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd40
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt24
-rw-r--r--services/credentials/java/com/android/server/credentials/ClearRequestSession.java3
-rw-r--r--services/credentials/java/com/android/server/credentials/CreateRequestSession.java2
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerService.java3
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java8
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerUi.java1
-rw-r--r--services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java3
-rw-r--r--services/credentials/java/com/android/server/credentials/GetRequestSession.java3
-rw-r--r--services/credentials/java/com/android/server/credentials/MetricUtilities.java3
-rw-r--r--services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java3
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderClearSession.java3
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderCreateSession.java3
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderGetSession.java3
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java3
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderSession.java3
-rw-r--r--services/credentials/java/com/android/server/credentials/RemoteCredentialService.java3
-rw-r--r--services/credentials/java/com/android/server/credentials/RequestSession.java3
-rw-r--r--services/devicepolicy/TEST_MAPPING7
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java30
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java77
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java41
-rw-r--r--services/java/com/android/server/SystemServer.java268
-rw-r--r--services/java/com/android/server/flags.aconfig1
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessPolicy.kt9
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt2
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/PermissionService.kt29
-rw-r--r--services/proguard.flags21
-rw-r--r--services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java60
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java36
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java67
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java8
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java92
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java415
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java28
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java83
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java7
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java26
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java121
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java18
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java75
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java71
-rw-r--r--services/tests/powerstatstests/Android.bp70
-rw-r--r--services/tests/powerstatstests/TEST_MAPPING6
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java63
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java10
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java8
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java22
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java18
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java3
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java2
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java6
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java16
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java23
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java12
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java64
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java18
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java189
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java14
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsProcessorTest.java (renamed from services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java)17
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java34
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java122
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java497
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java499
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java14
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java231
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java12
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java87
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java45
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsProcessorTest.java (renamed from services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java)16
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java6
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java90
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java15
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java37
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java24
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java65
-rw-r--r--services/tests/vibrator/AndroidManifest.xml2
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java24
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java64
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java94
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java88
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java1
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java11
-rw-r--r--telephony/common/com/android/internal/telephony/TelephonyPermissions.java13
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java52
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java2
-rw-r--r--telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl6
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java18
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java13
-rw-r--r--tests/CompanionDeviceMultiDeviceTests/client/Android.bp3
-rw-r--r--tests/CompanionDeviceMultiDeviceTests/host/Android.bp3
-rw-r--r--tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml4
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt4
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt25
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt4
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt4
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt20
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt32
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt16
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt46
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt45
-rw-r--r--tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt9
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt7
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt5
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt2
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt2
-rw-r--r--tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt4
-rw-r--r--tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt4
-rw-r--r--tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt4
-rw-r--r--tests/FlickerTests/README.md75
-rw-r--r--tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt44
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt5
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt8
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt8
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt27
-rw-r--r--tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt1067
-rw-r--r--tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java4
-rw-r--r--tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java122
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java3
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java6
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java7
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java6
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java6
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java5
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java5
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java6
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java27
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java48
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java112
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java6
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java27
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java6
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java6
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java22
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java24
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java59
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java51
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java6
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java7
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java49
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java50
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java6
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java7
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java149
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java14
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java3
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java225
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java17
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java80
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java75
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java92
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml17
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml14
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml8
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml8
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml4
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml8
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml31
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml8
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-actions-in-app.xml31
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-app-performance.xml21
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-audio.xml21
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-calendar.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-contacts.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-email-text-message.xml21
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-financial.xml26
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-health-fitness.xml16
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-identifiers.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-location.xml16
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-empty-purpose.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-missing-purpose.xml8
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml17
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml54
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-photo-video.xml16
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-search-and-browsing.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-storage.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized-type.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-collected-shared.xml29
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-invalid-bool.xml13
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml2
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml6
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml4
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml7
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml6
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/securitylabels/hr/all-fields-valid.xml4
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/securitylabels/od/all-fields-valid.xml4
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/hr/missing-url.xml1
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/hr/valid.xml1
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/od/valid.xml3
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml35
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml70
-rw-r--r--tools/hoststubgen/hoststubgen/Android.bp23
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt54
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt (renamed from tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt)12
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt2
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt5
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt11
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt113
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt4
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt24
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt11
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt46
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt11
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt56
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java2
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java4
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java11
-rw-r--r--wifi/wifi.aconfig1
1164 files changed, 29220 insertions, 11698 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 4c235e44fd1d..6975b557413c 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -159,7 +159,6 @@ cc_aconfig_library {
aconfig_declarations {
name: "com.android.window.flags.window-aconfig",
package: "com.android.window.flags",
- container: "system",
srcs: ["core/java/android/window/flags/*.aconfig"],
}
@@ -174,7 +173,6 @@ aconfig_declarations {
name: "android.hardware.devicestate.feature.flags-aconfig",
exportable: true,
package: "android.hardware.devicestate.feature.flags",
- container: "system",
srcs: ["core/java/android/hardware/devicestate/feature/*.aconfig"],
}
@@ -189,7 +187,6 @@ aconfig_declarations {
name: "com.android.hardware.input.input-aconfig",
exportable: true,
package: "com.android.hardware.input",
- container: "system",
srcs: ["core/java/android/hardware/input/*.aconfig"],
}
@@ -209,7 +206,6 @@ java_aconfig_library {
aconfig_declarations {
name: "com.android.text.flags-aconfig",
package: "com.android.text.flags",
- container: "system",
srcs: ["core/java/android/text/flags/*.aconfig"],
}
@@ -228,7 +224,6 @@ cc_aconfig_library {
aconfig_declarations {
name: "android.location.flags-aconfig",
package: "android.location.flags",
- container: "system",
srcs: [
"location/java/android/location/flags/*.aconfig",
],
@@ -250,7 +245,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.nfc.flags-aconfig",
package: "android.nfc",
- container: "system",
srcs: ["nfc/java/android/nfc/*.aconfig"],
}
@@ -281,7 +275,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.security.flags-aconfig",
package: "android.security",
- container: "system",
srcs: ["core/java/android/security/*.aconfig"],
}
@@ -302,7 +295,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.app.usage.flags-aconfig",
package: "android.app.usage",
- container: "system",
srcs: ["core/java/android/app/usage/*.aconfig"],
}
@@ -386,7 +378,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.companion.virtualdevice.flags-aconfig",
package: "android.companion.virtualdevice.flags",
- container: "system",
srcs: ["core/java/android/companion/virtual/flags/*.aconfig"],
}
@@ -399,7 +390,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.companion.virtual.flags-aconfig",
package: "android.companion.virtual.flags",
- container: "system",
srcs: ["core/java/android/companion/virtual/*.aconfig"],
}
@@ -407,7 +397,6 @@ aconfig_declarations {
aconfig_declarations {
name: "android.view.inputmethod.flags-aconfig",
package: "android.view.inputmethod",
- container: "system",
srcs: ["core/java/android/view/inputmethod/flags.aconfig"],
}
@@ -421,7 +410,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.os.vibrator.flags-aconfig",
package: "android.os.vibrator",
- container: "system",
srcs: ["core/java/android/os/vibrator/*.aconfig"],
}
@@ -435,7 +423,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.view.flags-aconfig",
package: "android.view.flags",
- container: "system",
srcs: ["core/java/android/view/flags/*.aconfig"],
}
@@ -454,7 +441,6 @@ cc_aconfig_library {
aconfig_declarations {
name: "android.view.accessibility.flags-aconfig",
package: "android.view.accessibility",
- container: "system",
srcs: ["core/java/android/view/accessibility/flags/*.aconfig"],
}
@@ -474,7 +460,6 @@ aconfig_declarations {
name: "android.hardware.flags-aconfig",
exportable: true,
package: "android.hardware.flags",
- container: "system",
srcs: ["core/java/android/hardware/flags/*.aconfig"],
}
@@ -488,7 +473,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.widget.flags-aconfig",
package: "android.widget.flags",
- container: "system",
srcs: ["core/java/android/widget/flags/*.aconfig"],
}
@@ -508,7 +492,6 @@ rust_aconfig_library {
aconfig_declarations {
name: "android.content.pm.flags-aconfig",
package: "android.content.pm",
- container: "system",
srcs: ["core/java/android/content/pm/flags.aconfig"],
}
@@ -529,7 +512,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.content.res.flags-aconfig",
package: "android.content.res",
- container: "system",
srcs: ["core/java/android/content/res/*.aconfig"],
}
@@ -550,7 +532,6 @@ java_aconfig_library {
aconfig_declarations {
name: "com.android.media.flags.bettertogether-aconfig",
package: "com.android.media.flags",
- container: "system",
srcs: ["media/java/android/media/flags/media_better_together.aconfig"],
}
@@ -572,7 +553,6 @@ aconfig_declarations {
name: "com.android.media.flags.editing-aconfig",
exportable: true,
package: "com.android.media.editing.flags",
- container: "system",
srcs: [
"media/java/android/media/flags/editing.aconfig",
],
@@ -588,7 +568,6 @@ java_aconfig_library {
aconfig_declarations {
name: "com.android.media.flags.projection-aconfig",
package: "com.android.media.projection.flags",
- container: "system",
srcs: [
"media/java/android/media/flags/projection.aconfig",
],
@@ -620,7 +599,6 @@ aconfig_declarations {
name: "android.media.tv.flags-aconfig",
exportable: true,
package: "android.media.tv.flags",
- container: "system",
srcs: ["media/java/android/media/tv/flags/media_tv.aconfig"],
}
@@ -635,7 +613,6 @@ aconfig_declarations {
name: "android.app.ondeviceintelligence-aconfig",
exportable: true,
package: "android.app.ondeviceintelligence.flags",
- container: "system",
srcs: ["core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig"],
}
@@ -649,7 +626,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.permission.flags-aconfig",
package: "android.permission.flags",
- container: "system",
srcs: ["core/java/android/permission/flags.aconfig"],
}
@@ -669,7 +645,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.database.sqlite-aconfig",
package: "android.database.sqlite",
- container: "system",
srcs: ["core/java/android/database/sqlite/*.aconfig"],
}
@@ -690,7 +665,6 @@ aconfig_declarations {
name: "android.hardware.biometrics.flags-aconfig",
exportable: true,
package: "android.hardware.biometrics",
- container: "system",
srcs: ["core/java/android/hardware/biometrics/flags.aconfig"],
}
@@ -742,7 +716,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.multiuser.flags-aconfig",
package: "android.multiuser",
- container: "system",
srcs: ["core/java/android/content/pm/multiuser.aconfig"],
}
@@ -756,7 +729,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.app.flags-aconfig",
package: "android.app",
- container: "system",
srcs: ["core/java/android/app/*.aconfig"],
}
@@ -771,7 +743,6 @@ aconfig_declarations {
name: "android.hardware.radio.flags-aconfig",
exportable: true,
package: "android.hardware.radio",
- container: "system",
srcs: ["core/java/android/hardware/radio/*.aconfig"],
}
@@ -785,7 +756,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.credentials.flags-aconfig",
package: "android.credentials.flags",
- container: "system",
srcs: ["core/java/android/credentials/flags.aconfig"],
exportable: true,
}
@@ -808,7 +778,6 @@ aconfig_declarations {
name: "android.view.contentprotection.flags-aconfig",
exportable: true,
package: "android.view.contentprotection.flags",
- container: "system",
srcs: ["core/java/android/view/contentprotection/flags/*.aconfig"],
}
@@ -822,7 +791,6 @@ java_aconfig_library {
aconfig_declarations {
name: "com.android.server.flags.services-aconfig",
package: "com.android.server.flags",
- container: "system",
srcs: ["services/core/java/com/android/server/flags/*.aconfig"],
}
@@ -837,7 +805,6 @@ aconfig_declarations {
name: "android.service.appprediction.flags-aconfig",
exportable: true,
package: "android.service.appprediction.flags",
- container: "system",
srcs: ["core/java/android/service/appprediction/flags/*.aconfig"],
}
@@ -852,7 +819,6 @@ aconfig_declarations {
name: "android.service.controls.flags-aconfig",
exportable: true,
package: "android.service.controls.flags",
- container: "system",
srcs: ["core/java/android/service/controls/flags/*.aconfig"],
}
@@ -867,7 +833,6 @@ aconfig_declarations {
name: "android.service.voice.flags-aconfig",
exportable: true,
package: "android.service.voice.flags",
- container: "system",
srcs: ["core/java/android/service/voice/flags/*.aconfig"],
}
@@ -881,7 +846,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.service.autofill.flags-aconfig",
package: "android.service.autofill",
- container: "system",
srcs: [
"services/autofill/bugfixes.aconfig",
"services/autofill/features.aconfig",
@@ -899,7 +863,6 @@ aconfig_declarations {
name: "android.companion.flags-aconfig",
exportable: true,
package: "android.companion",
- container: "system",
srcs: ["core/java/android/companion/*.aconfig"],
}
@@ -914,7 +877,6 @@ aconfig_declarations {
name: "android.net.platform.flags-aconfig",
exportable: true,
package: "android.net.platform.flags",
- container: "system",
srcs: ["core/java/android/net/flags.aconfig"],
visibility: [":__subpackages__"],
}
@@ -924,7 +886,6 @@ aconfig_declarations {
name: "com.android.net.thread.platform.flags-aconfig",
exportable: true,
package: "com.android.net.thread.platform.flags",
- container: "system",
srcs: ["core/java/android/net/thread/flags.aconfig"],
}
@@ -945,7 +906,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.media.playback.flags-aconfig",
package: "com.android.media.playback.flags",
- container: "system",
srcs: ["media/jni/playback_flags.aconfig"],
}
@@ -964,7 +924,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.net.vcn.flags-aconfig",
package: "android.net.vcn",
- container: "system",
srcs: ["core/java/android/net/vcn/*.aconfig"],
}
@@ -978,7 +937,6 @@ java_aconfig_library {
aconfig_declarations {
name: "device_policy_aconfig_flags",
package: "android.app.admin.flags",
- container: "system",
srcs: [
"core/java/android/app/admin/flags/flags.aconfig",
],
@@ -1006,7 +964,6 @@ cc_aconfig_library {
aconfig_declarations {
name: "android.service.chooser.flags-aconfig",
package: "android.service.chooser",
- container: "system",
srcs: ["core/java/android/service/chooser/flags.aconfig"],
}
@@ -1025,7 +982,6 @@ java_aconfig_library {
aconfig_declarations {
name: "framework-jobscheduler-job.flags-aconfig",
package: "android.app.job",
- container: "system",
exportable: true,
srcs: ["apex/jobscheduler/framework/aconfig/job.aconfig"],
}
@@ -1040,7 +996,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.service.dreams.flags-aconfig",
package: "android.service.dreams",
- container: "system",
srcs: ["core/java/android/service/dreams/flags.aconfig"],
}
@@ -1081,7 +1036,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.app.contextualsearch.flags-aconfig",
package: "android.app.contextualsearch.flags",
- container: "system",
srcs: ["core/java/android/app/contextualsearch/flags.aconfig"],
}
@@ -1096,7 +1050,6 @@ aconfig_declarations {
name: "android.app.smartspace.flags-aconfig",
exportable: true,
package: "android.app.smartspace.flags",
- container: "system",
srcs: ["core/java/android/app/smartspace/flags.aconfig"],
}
@@ -1117,7 +1070,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.view.contentcapture.flags-aconfig",
package: "android.view.contentcapture.flags",
- container: "system",
srcs: ["core/java/android/view/contentcapture/flags/*.aconfig"],
}
@@ -1132,7 +1084,6 @@ aconfig_declarations {
name: "android.hardware.usb.flags-aconfig",
exportable: true,
package: "android.hardware.usb.flags",
- container: "system",
srcs: ["core/java/android/hardware/usb/flags/*.aconfig"],
}
@@ -1153,7 +1104,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.tracing.flags-aconfig",
package: "android.tracing",
- container: "system",
srcs: ["core/java/android/tracing/flags.aconfig"],
}
@@ -1172,7 +1122,6 @@ cc_aconfig_library {
aconfig_declarations {
name: "android.appwidget.flags-aconfig",
package: "android.appwidget.flags",
- container: "system",
srcs: ["core/java/android/appwidget/flags.aconfig"],
}
@@ -1186,7 +1135,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.server.app.flags-aconfig",
package: "android.server.app",
- container: "system",
srcs: ["services/core/java/com/android/server/app/flags.aconfig"],
}
@@ -1200,7 +1148,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.webkit.flags-aconfig",
package: "android.webkit",
- container: "system",
srcs: [
"core/java/android/webkit/*.aconfig",
"services/core/java/com/android/server/webkit/*.aconfig",
@@ -1218,7 +1165,6 @@ aconfig_declarations {
name: "android.provider.flags-aconfig",
exportable: true,
package: "android.provider",
- container: "system",
srcs: ["core/java/android/provider/*.aconfig"],
}
@@ -1240,7 +1186,6 @@ aconfig_declarations {
name: "android.speech.flags-aconfig",
exportable: true,
package: "android.speech.flags",
- container: "system",
srcs: ["core/java/android/speech/flags/*.aconfig"],
}
@@ -1262,7 +1207,6 @@ aconfig_declarations {
name: "android.content.flags-aconfig",
exportable: true,
package: "android.content.flags",
- container: "system",
srcs: ["core/java/android/content/flags/flags.aconfig"],
}
@@ -1276,7 +1220,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.adaptiveauth.flags-aconfig",
package: "android.adaptiveauth",
- container: "system",
srcs: ["core/java/android/adaptiveauth/*.aconfig"],
}
@@ -1291,7 +1234,6 @@ aconfig_declarations {
name: "android.crashrecovery.flags-aconfig",
exportable: true,
package: "android.crashrecovery.flags",
- container: "system",
srcs: ["packages/CrashRecovery/aconfig/flags.aconfig"],
}
@@ -1312,7 +1254,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.net.wifi.flags-aconfig",
package: "android.net.wifi.flags",
- container: "system",
srcs: ["wifi/*.aconfig"],
}
@@ -1332,7 +1273,6 @@ aconfig_declarations {
name: "android.app.wearable.flags-aconfig",
exportable: true,
package: "android.app.wearable",
- container: "system",
srcs: ["core/java/android/app/wearable/*.aconfig"],
}
@@ -1345,7 +1285,6 @@ java_aconfig_library {
aconfig_declarations {
name: "com.android.internal.pm.pkg.component.flags-aconfig",
package: "com.android.internal.pm.pkg.component.flags",
- container: "system",
srcs: ["core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig"],
}
@@ -1366,7 +1305,6 @@ java_aconfig_library {
aconfig_declarations {
name: "android.systemserver.flags-aconfig",
package: "android.server",
- container: "system",
srcs: ["services/java/com/android/server/flags.aconfig"],
}
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 68717623d05d..762e2af09cd3 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -366,7 +366,7 @@ public class UserLifecycleTests {
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
- stopUser(userId, /* force */true);
+ stopUser(userId);
mRunner.resumeTimingForNextIteration();
}
@@ -429,7 +429,7 @@ public class UserLifecycleTests {
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
- stopUser(userId, /* force */true);
+ stopUser(userId);
mRunner.resumeTimingForNextIteration();
}
@@ -545,7 +545,7 @@ public class UserLifecycleTests {
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
switchUserNoCheck(currentUserId);
- stopUserAfterWaitingForBroadcastIdle(userId, /* force */true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
attestFalse("Failed to stop user " + userId, mAm.isUserRunning(userId));
mRunner.resumeTimingForNextIteration();
}
@@ -571,7 +571,7 @@ public class UserLifecycleTests {
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
switchUserNoCheck(startUser);
- stopUserAfterWaitingForBroadcastIdle(testUser, true);
+ stopUserAfterWaitingForBroadcastIdle(testUser);
attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
mRunner.resumeTimingForNextIteration();
}
@@ -660,7 +660,7 @@ public class UserLifecycleTests {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
- stopUser(userId, false);
+ stopUser(userId);
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
@@ -685,7 +685,7 @@ public class UserLifecycleTests {
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
- stopUser(userId, false);
+ stopUser(userId);
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
@@ -883,7 +883,7 @@ public class UserLifecycleTests {
final int userId = createManagedProfile();
// Start the profile initially, then stop it. Similar to setQuietModeEnabled.
startUserInBackgroundAndWaitForUnlock(userId);
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -905,7 +905,7 @@ public class UserLifecycleTests {
startUserInBackgroundAndWaitForUnlock(userId);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
@@ -987,7 +987,7 @@ public class UserLifecycleTests {
installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
startUserInBackgroundAndWaitForUnlock(userId);
startApp(userId, DUMMY_PACKAGE_NAME);
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
SystemClock.sleep(1_000); // 1 second cool-down before re-starting profile.
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -1019,7 +1019,7 @@ public class UserLifecycleTests {
installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
startUserInBackgroundAndWaitForUnlock(userId);
startApp(userId, DUMMY_PACKAGE_NAME);
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
SystemClock.sleep(1_000); // 1 second cool-down before re-starting profile.
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
@@ -1144,7 +1144,7 @@ public class UserLifecycleTests {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
- stopUser(userId, true);
+ stopUser(userId);
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
@@ -1168,7 +1168,7 @@ public class UserLifecycleTests {
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
- stopUser(userId, true);
+ stopUser(userId);
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
@@ -1294,15 +1294,15 @@ public class UserLifecycleTests {
* Do not call this method while timing is on. i.e. between mRunner.resumeTiming() and
* mRunner.pauseTiming(). Otherwise it would cause the test results to be spiky.
*/
- private void stopUserAfterWaitingForBroadcastIdle(int userId, boolean force)
+ private void stopUserAfterWaitingForBroadcastIdle(int userId)
throws RemoteException {
waitForBroadcastIdle();
- stopUser(userId, force);
+ stopUser(userId);
}
- private void stopUser(int userId, boolean force) throws RemoteException {
+ private void stopUser(int userId) throws RemoteException {
final CountDownLatch latch = new CountDownLatch(1);
- mIam.stopUser(userId, force /* force */, new IStopUserCallback.Stub() {
+ mIam.stopUserWithCallback(userId, new IStopUserCallback.Stub() {
@Override
public void userStopped(int userId) throws RemoteException {
latch.countDown();
@@ -1352,7 +1352,7 @@ public class UserLifecycleTests {
attestTrue("Didn't switch back to user, " + origUser, origUser == mAm.getCurrentUser());
if (stopNewUser) {
- stopUserAfterWaitingForBroadcastIdle(testUser, true);
+ stopUserAfterWaitingForBroadcastIdle(testUser);
attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
}
@@ -1471,7 +1471,7 @@ public class UserLifecycleTests {
}
private void removeUser(int userId) throws RemoteException {
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
try {
ShellHelper.runShellCommandWithTimeout("pm remove-user -w " + userId,
TIMEOUT_IN_SECOND);
@@ -1512,7 +1512,7 @@ public class UserLifecycleTests {
final boolean preStartComplete = mIam.startUserInBackgroundWithListener(userId,
preWaiter) && preWaiter.waitForFinish(TIMEOUT_IN_SECOND * 1000);
- stopUserAfterWaitingForBroadcastIdle(userId, /* force */true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
assertTrue("Pre start was not performed for user" + userId, preStartComplete);
}
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index 80db264d0f44..2c1a8532568c 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -1,5 +1,4 @@
package: "android.app.job"
-container: "system"
flag {
name: "enforce_minimum_time_windows"
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index ace56d42ddd1..06c7d64d1708 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -24,6 +24,7 @@ java_library {
"app-compat-annotations",
"error_prone_annotations",
"framework",
+ "keepanno-annotations",
"services.core",
"unsupportedappusage",
],
diff --git a/apex/jobscheduler/service/aconfig/device_idle.aconfig b/apex/jobscheduler/service/aconfig/device_idle.aconfig
index e8c99b12828f..c4d0d1850a18 100644
--- a/apex/jobscheduler/service/aconfig/device_idle.aconfig
+++ b/apex/jobscheduler/service/aconfig/device_idle.aconfig
@@ -2,6 +2,16 @@ package: "com.android.server.deviceidle"
container: "system"
flag {
+ name: "remove_idle_location"
+ namespace: "location"
+ description: "Remove DeviceIdleController usage of location"
+ bug: "332770178"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "disable_wakelocks_in_light_idle"
namespace: "backstage_power"
description: "Disable wakelocks for background apps while Light Device Idle is active"
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 4832ea624bd7..11fa7b75182f 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -109,6 +109,7 @@ import com.android.modules.expresslog.Counter;
import com.android.server.am.BatteryStatsService;
import com.android.server.deviceidle.ConstraintController;
import com.android.server.deviceidle.DeviceIdleConstraintTracker;
+import com.android.server.deviceidle.Flags;
import com.android.server.deviceidle.IDeviceIdleConstraint;
import com.android.server.deviceidle.TvConstraintController;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -2558,7 +2559,7 @@ public class DeviceIdleController extends SystemService
}
boolean isLocationPrefetchEnabled() {
- return mContext.getResources().getBoolean(
+ return !Flags.removeIdleLocation() && mContext.getResources().getBoolean(
com.android.internal.R.bool.config_autoPowerModePrefetchLocation);
}
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 f9c8e0b551bd..b982d1253e21 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -1795,6 +1795,10 @@ public class AlarmManagerService extends SystemService {
mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms();
mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms();
+ if (mStartUserBeforeScheduledAlarms) {
+ mUserWakeupStore = new UserWakeupStore();
+ mUserWakeupStore.init();
+ }
if (mUseFrozenStateToDropListenerAlarms) {
final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> {
final int size = frozenStates.length;
@@ -1913,10 +1917,6 @@ public class AlarmManagerService extends SystemService {
Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
}
}
- if (mStartUserBeforeScheduledAlarms) {
- mUserWakeupStore = new UserWakeupStore();
- mUserWakeupStore.init();
- }
publishLocalService(AlarmManagerInternal.class, new LocalService());
publishBinderService(Context.ALARM_SERVICE, mService);
}
@@ -3863,7 +3863,7 @@ public class AlarmManagerService extends SystemService {
long nextNonWakeup = 0;
if (mAlarmStore.size() > 0) {
long firstWakeup = mAlarmStore.getNextWakeupDeliveryTime();
- if (mStartUserBeforeScheduledAlarms) {
+ if (mStartUserBeforeScheduledAlarms && mUserWakeupStore != null) {
final long firstUserWakeup = mUserWakeupStore.getNextWakeupTime();
if (firstUserWakeup >= 0 && firstUserWakeup < firstWakeup) {
firstWakeup = firstUserWakeup;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 096238aeda7c..012ede274bc1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1737,17 +1737,6 @@ class JobConcurrencyManager {
continue;
}
- if (!nextPending.isReady()) {
- // This could happen when the job count reached its quota, the constrains
- // for the job has been updated but hasn't been removed from the pending
- // queue yet.
- if (DEBUG) {
- Slog.w(TAG, "Pending+not ready job: " + nextPending);
- }
- pendingJobQueue.remove(nextPending);
- continue;
- }
-
if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
Slog.w(TAG, "Already running similar job to: " + nextPending);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index cfbfa5dce399..3c9648b20003 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -512,7 +512,7 @@ public final class QuotaController extends StateController {
/** An app has reached its quota. The message should contain a {@link UserPackage} object. */
@VisibleForTesting
- static final int MSG_REACHED_TIME_QUOTA = 0;
+ static final int MSG_REACHED_QUOTA = 0;
/** Drop any old timing sessions. */
private static final int MSG_CLEAN_UP_SESSIONS = 1;
/** Check if a package is now within its quota. */
@@ -524,7 +524,7 @@ public final class QuotaController extends StateController {
* object.
*/
@VisibleForTesting
- static final int MSG_REACHED_EJ_TIME_QUOTA = 4;
+ static final int MSG_REACHED_EJ_QUOTA = 4;
/**
* Process a new {@link UsageEvents.Event}. The event will be the message's object and the
* userId will the first arg.
@@ -533,11 +533,6 @@ public final class QuotaController extends StateController {
/** A UID's free quota grace period has ended. */
@VisibleForTesting
static final int MSG_END_GRACE_PERIOD = 6;
- /**
- * An app has reached its job count quota. The message should contain a {@link UserPackage}
- * object.
- */
- static final int MSG_REACHED_COUNT_QUOTA = 7;
public QuotaController(@NonNull JobSchedulerService service,
@NonNull BackgroundJobsController backgroundJobsController,
@@ -879,37 +874,17 @@ public final class QuotaController extends StateController {
}
@VisibleForTesting
- @GuardedBy("mLock")
boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
final int standbyBucket = jobStatus.getEffectiveStandbyBucket();
// A job is within quota if one of the following is true:
// 1. it was started while the app was in the TOP state
// 2. the app is currently in the foreground
// 3. the app overall is within its quota
- if (jobStatus.shouldTreatAsUserInitiatedJob()
+ return jobStatus.shouldTreatAsUserInitiatedJob()
|| isTopStartedJobLocked(jobStatus)
- || isUidInForeground(jobStatus.getSourceUid())) {
- return true;
- }
-
- if (standbyBucket == NEVER_INDEX) return false;
-
- if (isQuotaFreeLocked(standbyBucket)) return true;
-
- final ExecutionStats stats = getExecutionStatsLocked(jobStatus.getSourceUserId(),
- jobStatus.getSourcePackageName(), standbyBucket);
- if (!(getRemainingExecutionTimeLocked(stats) > 0)) {
- // Out of execution time quota.
- return false;
- }
-
- if (mService.isCurrentlyRunningLocked(jobStatus)) {
- // if job is running, considered as in quota so it can keep running.
- return true;
- }
-
- // Check if the app is within job count quota.
- return isUnderJobCountQuotaLocked(stats) && isUnderSessionCountQuotaLocked(stats);
+ || isUidInForeground(jobStatus.getSourceUid())
+ || isWithinQuotaLocked(
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
}
@GuardedBy("mLock")
@@ -934,11 +909,12 @@ public final class QuotaController extends StateController {
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
// TODO: use a higher minimum remaining time for jobs with MINIMUM priority
return getRemainingExecutionTimeLocked(stats) > 0
- && isUnderJobCountQuotaLocked(stats)
- && isUnderSessionCountQuotaLocked(stats);
+ && isUnderJobCountQuotaLocked(stats, standbyBucket)
+ && isUnderSessionCountQuotaLocked(stats, standbyBucket);
}
- private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats) {
+ private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
+ final int standbyBucket) {
final long now = sElapsedRealtimeClock.millis();
final boolean isUnderAllowedTimeQuota =
(stats.jobRateLimitExpirationTimeElapsed <= now
@@ -947,7 +923,8 @@ public final class QuotaController extends StateController {
&& stats.bgJobCountInWindow < stats.jobCountLimit;
}
- private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats) {
+ private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
+ final int standbyBucket) {
final long now = sElapsedRealtimeClock.millis();
final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
|| stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
@@ -1472,7 +1449,6 @@ public final class QuotaController extends StateController {
stats.jobCountInRateLimitingWindow = 0;
}
stats.jobCountInRateLimitingWindow += count;
- stats.bgJobCountInWindow += count;
}
}
@@ -1707,11 +1683,10 @@ public final class QuotaController extends StateController {
changedJobs.add(js);
}
} else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX
- && realStandbyBucket == js.getEffectiveStandbyBucket()
- && !mService.isCurrentlyRunningLocked(js)) {
+ && realStandbyBucket == js.getEffectiveStandbyBucket()) {
// An app in the ACTIVE bucket may be out of quota while the job could be in quota
// for some reason. Therefore, avoid setting the real value here and check each job
- // individually. Running job need to determine its own quota status as well.
+ // individually.
if (setConstraintSatisfied(js, nowElapsed, realInQuota, isWithinEJQuota)) {
changedJobs.add(js);
}
@@ -1830,8 +1805,9 @@ public final class QuotaController extends StateController {
}
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
- final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats);
- final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats);
+ final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
+ final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
+ standbyBucket);
final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName);
final boolean inRegularQuota =
@@ -2150,11 +2126,6 @@ public final class QuotaController extends StateController {
mBgJobCount++;
if (mRegularJobTimer) {
incrementJobCountLocked(mPkg.userId, mPkg.packageName, 1);
- final ExecutionStats stats = getExecutionStatsLocked(mPkg.userId,
- mPkg.packageName, jobStatus.getEffectiveStandbyBucket(), false);
- if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
- mHandler.obtainMessage(MSG_REACHED_COUNT_QUOTA, mPkg).sendToTarget();
- }
}
if (mRunningBgJobs.size() == 1) {
// Started tracking the first job.
@@ -2286,6 +2257,7 @@ public final class QuotaController extends StateController {
// repeatedly plugged in and unplugged, or an app changes foreground state
// very frequently, the job count for a package may be artificially high.
mBgJobCount = mRunningBgJobs.size();
+
if (mRegularJobTimer) {
incrementJobCountLocked(mPkg.userId, mPkg.packageName, mBgJobCount);
// Starting the timer means that all cached execution stats are now
@@ -2312,8 +2284,7 @@ public final class QuotaController extends StateController {
return;
}
Message msg = mHandler.obtainMessage(
- mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA,
- mPkg);
+ mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
final long timeRemainingMs = mRegularJobTimer
? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName)
: getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName);
@@ -2330,7 +2301,7 @@ public final class QuotaController extends StateController {
private void cancelCutoff() {
mHandler.removeMessages(
- mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA, mPkg);
+ mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
}
public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
@@ -2586,7 +2557,7 @@ public final class QuotaController extends StateController {
break;
default:
if (DEBUG) {
- Slog.d(TAG, "Dropping usage event " + event.getEventType());
+ Slog.d(TAG, "Dropping event " + event.getEventType());
}
break;
}
@@ -2695,7 +2666,7 @@ public final class QuotaController extends StateController {
public void handleMessage(Message msg) {
synchronized (mLock) {
switch (msg.what) {
- case MSG_REACHED_TIME_QUOTA: {
+ case MSG_REACHED_QUOTA: {
UserPackage pkg = (UserPackage) msg.obj;
if (DEBUG) {
Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
@@ -2714,7 +2685,7 @@ public final class QuotaController extends StateController {
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_TIME_QUOTA, pkg);
+ Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg);
timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId,
pkg.packageName);
if (DEBUG) {
@@ -2724,7 +2695,7 @@ public final class QuotaController extends StateController {
}
break;
}
- case MSG_REACHED_EJ_TIME_QUOTA: {
+ case MSG_REACHED_EJ_QUOTA: {
UserPackage pkg = (UserPackage) msg.obj;
if (DEBUG) {
Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota.");
@@ -2742,7 +2713,7 @@ public final class QuotaController extends StateController {
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_TIME_QUOTA, pkg);
+ Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg);
timeRemainingMs = getTimeUntilEJQuotaConsumedLocked(
pkg.userId, pkg.packageName);
if (DEBUG) {
@@ -2752,18 +2723,6 @@ public final class QuotaController extends StateController {
}
break;
}
- case MSG_REACHED_COUNT_QUOTA: {
- UserPackage pkg = (UserPackage) msg.obj;
- if (DEBUG) {
- Slog.d(TAG, pkg + " has reached its count quota.");
- }
-
- mStateChangedListener.onControllerStateChanged(
- maybeUpdateConstraintForPkgLocked(
- sElapsedRealtimeClock.millis(),
- pkg.userId, pkg.packageName));
- break;
- }
case MSG_CLEAN_UP_SESSIONS:
if (DEBUG) {
Slog.d(TAG, "Cleaning up timing sessions.");
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 19bc7160e16a..613678bedf8a 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -130,6 +130,8 @@ import com.android.server.AppSchedulingModuleThread;
import com.android.server.LocalServices;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.usage.AppIdleHistory.AppUsageHistory;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
import libcore.util.EmptyArray;
@@ -588,6 +590,8 @@ public class AppStandbyController
}
}
+ // This constructor is reflectively invoked from framework code in AppStandbyInternal.
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
public AppStandbyController(Context context) {
this(new Injector(context, AppSchedulingModuleThread.get().getLooper()));
}
diff --git a/apex/jobscheduler/service/jni/Android.bp b/apex/jobscheduler/service/jni/Android.bp
index 34a1fa2ebc13..e8acff739e52 100644
--- a/apex/jobscheduler/service/jni/Android.bp
+++ b/apex/jobscheduler/service/jni/Android.bp
@@ -28,4 +28,8 @@ cc_library_shared {
"liblog",
"libbase",
],
+ visibility: [
+ "//frameworks/base/apex:__subpackages__",
+ "//visibility:any_system_partition",
+ ],
}
diff --git a/api/coverage/tools/Android.bp b/api/coverage/tools/Android.bp
index 3e169120dc48..caaca99bdc45 100644
--- a/api/coverage/tools/Android.bp
+++ b/api/coverage/tools/Android.bp
@@ -30,3 +30,24 @@ java_library_host {
type: "full",
},
}
+
+java_test_host {
+ name: "extract-flagged-apis-test",
+ srcs: ["ExtractFlaggedApisTest.kt"],
+ libs: [
+ "extract_flagged_apis_proto",
+ "junit",
+ "libprotobuf-java-full",
+ ],
+ static_libs: [
+ "truth",
+ "truth-liteproto-extension",
+ "truth-proto-extension",
+ ],
+ data: [
+ ":extract-flagged-apis",
+ ],
+ test_options: {
+ unit_test: true,
+ },
+}
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index d5adfd09b994..5178f09f0301 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -28,12 +28,10 @@ fun main(args: Array<String>) {
val builder = FlagApiMap.newBuilder()
for (pkg in cb.getPackages().packages) {
val packageName = pkg.qualifiedName()
- pkg.allClasses()
- .filter { it.methods().size > 0 }
- .forEach {
- extractFlaggedApisFromClass(it, it.methods(), packageName, builder)
- extractFlaggedApisFromClass(it, it.constructors(), packageName, builder)
- }
+ pkg.allClasses().forEach {
+ extractFlaggedApisFromClass(it, it.methods(), packageName, builder)
+ extractFlaggedApisFromClass(it, it.constructors(), packageName, builder)
+ }
}
val flagApiMap = builder.build()
FileWriter(args[1]).use { it.write(flagApiMap.toString()) }
@@ -45,6 +43,7 @@ fun extractFlaggedApisFromClass(
packageName: String,
builder: FlagApiMap.Builder
) {
+ if (methods.isEmpty()) return
val classFlag =
classItem.modifiers
.findAnnotation("android.annotation.FlaggedApi")
diff --git a/api/coverage/tools/ExtractFlaggedApisTest.kt b/api/coverage/tools/ExtractFlaggedApisTest.kt
new file mode 100644
index 000000000000..ee5aaf15cb57
--- /dev/null
+++ b/api/coverage/tools/ExtractFlaggedApisTest.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.platform.coverage
+
+import com.google.common.truth.extensions.proto.ProtoTruth.assertThat
+import com.google.protobuf.TextFormat
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.StandardOpenOption
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ExtractFlaggedApisTest {
+
+ companion object {
+ const val COMMAND = "java -jar extract-flagged-apis.jar %s %s"
+ }
+
+ private var apiTextFile: Path = Files.createTempFile("current", ".txt")
+ private var flagToApiMap: Path = Files.createTempFile("flag_api_map", ".textproto")
+
+ @Before
+ fun setup() {
+ apiTextFile = Files.createTempFile("current", ".txt")
+ flagToApiMap = Files.createTempFile("flag_api_map", ".textproto")
+ }
+
+ @After
+ fun cleanup() {
+ Files.deleteIfExists(apiTextFile)
+ Files.deleteIfExists(flagToApiMap)
+ }
+
+ @Test
+ fun extractFlaggedApis_onlyMethodFlag_useMethodFlag() {
+ val apiText =
+ """
+ // Signature format: 2.0
+ package android.net.ipsec.ike {
+ public final class IkeSession implements java.lang.AutoCloseable {
+ method @FlaggedApi("com.android.ipsec.flags.dumpsys_api") public void dump(@NonNull java.io.PrintWriter);
+ }
+ }
+ """
+ .trimIndent()
+ Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
+
+ val process = Runtime.getRuntime().exec(createCommand())
+ process.waitFor()
+
+ val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
+ val result = TextFormat.parse(content, FlagApiMap::class.java)
+
+ val expected = FlagApiMap.newBuilder()
+ val api =
+ JavaMethod.newBuilder()
+ .setPackageName("android.net.ipsec.ike")
+ .setClassName("IkeSession")
+ .setMethodName("dump")
+ api.addParameters("java.io.PrintWriter")
+ addFlaggedApi(expected, api, "com.android.ipsec.flags.dumpsys_api")
+ assertThat(result).isEqualTo(expected.build())
+ }
+
+ @Test
+ fun extractFlaggedApis_onlyClassFlag_useClassFlag() {
+ val apiText =
+ """
+ // Signature format: 2.0
+ package android.net.ipsec.ike {
+ @FlaggedApi("com.android.ipsec.flags.dumpsys_api") public final class IkeSession implements java.lang.AutoCloseable {
+ method public void dump(@NonNull java.io.PrintWriter);
+ }
+ }
+ """
+ .trimIndent()
+ Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
+
+ val process = Runtime.getRuntime().exec(createCommand())
+ process.waitFor()
+
+ val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
+ val result = TextFormat.parse(content, FlagApiMap::class.java)
+
+ val expected = FlagApiMap.newBuilder()
+ val api =
+ JavaMethod.newBuilder()
+ .setPackageName("android.net.ipsec.ike")
+ .setClassName("IkeSession")
+ .setMethodName("dump")
+ api.addParameters("java.io.PrintWriter")
+ addFlaggedApi(expected, api, "com.android.ipsec.flags.dumpsys_api")
+ assertThat(result).isEqualTo(expected.build())
+ }
+
+ @Test
+ fun extractFlaggedApis_flaggedConstructorsAreFlaggedApis() {
+ val apiText =
+ """
+ // Signature format: 2.0
+ package android.app.pinner {
+ @FlaggedApi("android.app.pinner_service_client_api") public class PinnerServiceClient {
+ ctor @FlaggedApi("android.app.pinner_service_client_api") public PinnerServiceClient();
+ }
+ }
+ """
+ .trimIndent()
+ Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
+
+ val process = Runtime.getRuntime().exec(createCommand())
+ process.waitFor()
+
+ val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
+ val result = TextFormat.parse(content, FlagApiMap::class.java)
+
+ val expected = FlagApiMap.newBuilder()
+ val api =
+ JavaMethod.newBuilder()
+ .setPackageName("android.app.pinner")
+ .setClassName("PinnerServiceClient")
+ .setMethodName("PinnerServiceClient")
+ addFlaggedApi(expected, api, "android.app.pinner_service_client_api")
+ assertThat(result).isEqualTo(expected.build())
+ }
+
+ private fun addFlaggedApi(builder: FlagApiMap.Builder, api: JavaMethod.Builder, flag: String) {
+ if (builder.containsFlagToApi(flag)) {
+ val updatedApis =
+ builder.getFlagToApiOrThrow(flag).toBuilder().addJavaMethods(api).build()
+ builder.putFlagToApi(flag, updatedApis)
+ } else {
+ val apis = FlaggedApis.newBuilder().addJavaMethods(api).build()
+ builder.putFlagToApi(flag, apis)
+ }
+ }
+
+ private fun createCommand(): Array<String> {
+ val command =
+ String.format(COMMAND, apiTextFile.toAbsolutePath(), flagToApiMap.toAbsolutePath())
+ return command.split(" ").toTypedArray()
+ }
+}
diff --git a/cmds/locksettings/TEST_MAPPING b/cmds/locksettings/TEST_MAPPING
index 7a449effdf76..af54a2decd89 100644
--- a/cmds/locksettings/TEST_MAPPING
+++ b/cmds/locksettings/TEST_MAPPING
@@ -11,5 +11,10 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsDevicePolicyManagerTestCases_LockSettings_NoFlakes"
+ }
]
}
diff --git a/core/api/current.txt b/core/api/current.txt
index b19c3ab96bb5..c189a24c84ae 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9877,7 +9877,7 @@ package android.companion {
ctor public ObservingDevicePresenceRequest.Builder();
method @NonNull public android.companion.ObservingDevicePresenceRequest build();
method @NonNull public android.companion.ObservingDevicePresenceRequest.Builder setAssociationId(int);
- method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE) public android.companion.ObservingDevicePresenceRequest.Builder setUuid(@NonNull android.os.ParcelUuid);
+ method @NonNull @RequiresPermission(allOf={android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN}) public android.companion.ObservingDevicePresenceRequest.Builder setUuid(@NonNull android.os.ParcelUuid);
}
public final class WifiDeviceFilter implements android.companion.DeviceFilter<android.net.wifi.ScanResult> {
@@ -54479,7 +54479,6 @@ package android.view {
field @FlaggedApi("com.android.window.flags.cover_display_opt_in") public static final int COMPAT_SMALL_COVER_SCREEN_OPT_IN = 1; // 0x1
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
- field @FlaggedApi("com.android.window.flags.untrusted_embedding_state_sharing") public static final String PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING = "android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING";
field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
field public static final String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f36aeab2cb17..35ab5f014815 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -157,7 +157,7 @@ package android.app {
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean startUserInBackgroundVisibleOnDisplay(int, int);
- method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
+ method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void unregisterUidFrozenStateChangedCallback(@NonNull android.app.ActivityManager.UidFrozenStateChangedCallback);
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
@@ -589,6 +589,7 @@ package android.app.admin {
method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs();
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int);
method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs();
+ method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_internal_bug_fix_enabled") @RequiresPermission("android.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT") public void forceSetMaxPolicyStorageLimit(int);
method public void forceUpdateUserSetupComplete(int);
method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages();
method @Deprecated public int getDeviceOwnerType(@NonNull android.content.ComponentName);
@@ -599,6 +600,7 @@ package android.app.admin {
method public long getLastSecurityLogRetrievalTime();
method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public java.util.Set<java.lang.String> getPolicyExemptApps();
+ method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_internal_bug_fix_enabled") @RequiresPermission("android.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT") public int getPolicySizeForAdmin(@NonNull android.app.admin.EnforcingAdmin);
method public boolean isCurrentInputMethodSetByOwner();
method public boolean isFactoryResetProtectionPolicySupported();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isNewUserDisclaimerAcknowledged();
@@ -667,6 +669,10 @@ package android.app.admin {
field @NonNull public static final android.app.admin.DpcAuthority DPC_AUTHORITY;
}
+ public final class EnforcingAdmin implements android.os.Parcelable {
+ ctor @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_internal_bug_fix_enabled") public EnforcingAdmin(@NonNull String, @NonNull android.app.admin.Authority, @NonNull android.os.UserHandle, @Nullable android.content.ComponentName);
+ }
+
public final class FlagUnion extends android.app.admin.ResolutionMechanism<java.lang.Integer> {
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -1765,19 +1771,22 @@ package android.hardware.input {
public final class InputManager {
method public void addUniqueIdAssociation(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings();
- method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier);
- method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice);
+ method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptors();
method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
method public int getMousePointerSpeed();
method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
- method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
method public void removeUniqueIdAssociation(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
}
public class InputSettings {
+ method @FlaggedApi("com.android.hardware.input.keyboard_a11y_bounce_keys_flag") public static int getAccessibilityBounceKeysThreshold(@NonNull android.content.Context);
+ method @FlaggedApi("com.android.hardware.input.keyboard_a11y_slow_keys_flag") public static int getAccessibilitySlowKeysThreshold(@NonNull android.content.Context);
+ method @FlaggedApi("com.android.hardware.input.keyboard_a11y_sticky_keys_flag") public static boolean isAccessibilityStickyKeysEnabled(@NonNull android.content.Context);
+ method @FlaggedApi("com.android.hardware.input.keyboard_a11y_bounce_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityBounceKeysThreshold(@NonNull android.content.Context, int);
+ method @FlaggedApi("com.android.hardware.input.keyboard_a11y_slow_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilitySlowKeysThreshold(@NonNull android.content.Context, int);
+ method @FlaggedApi("com.android.hardware.input.keyboard_a11y_sticky_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityStickyKeysEnabled(@NonNull android.content.Context, boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void setMaximumObscuringOpacityForTouch(@NonNull android.content.Context, @FloatRange(from=0, to=1) float);
field public static final int DEFAULT_POINTER_SPEED = 0; // 0x0
}
diff --git a/core/java/android/adaptiveauth/flags.aconfig b/core/java/android/adaptiveauth/flags.aconfig
index b9cf29cc13dd..de4e607b50f1 100644
--- a/core/java/android/adaptiveauth/flags.aconfig
+++ b/core/java/android/adaptiveauth/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.adaptiveauth"
-container: "system"
flag {
name: "enable_adaptive_auth"
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 77255617af0a..0dab3de599dc 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -5071,7 +5071,7 @@ public class ActivityManager {
* <p><b>NOTE:</b> differently from {@link #switchUser(int)}, which stops the current foreground
* user before starting a new one, this method does not stop the previous user running in
* background in the display, and it will return {@code false} in this case. It's up to the
- * caller to call {@link #stopUser(int, boolean)} before starting a new user.
+ * caller to call {@link #stopUser(int)} before starting a new user.
*
* @param userId user to be started in the display. It will return {@code false} if the user is
* a profile, the {@link #getCurrentUser()}, the {@link UserHandle#SYSTEM system user}, or
@@ -5281,15 +5281,16 @@ public class ActivityManager {
*
* @hide
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@TestApi
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
- public boolean stopUser(@UserIdInt int userId, boolean force) {
+ public boolean stopUser(@UserIdInt int userId) {
if (userId == UserHandle.USER_SYSTEM) {
return false;
}
try {
- return USER_OP_SUCCESS == getService().stopUser(
- userId, force, /* callback= */ null);
+ return USER_OP_SUCCESS == getService().stopUserWithCallback(
+ userId, /* callback= */ null);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index e094ac61b500..9ea55f5ff84c 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -2205,19 +2205,6 @@ public class ActivityOptions extends ComponentOptions {
}
/**
- * Sets background activity launch logic won't use pending intent creator foreground state.
- *
- * @hide
- * @deprecated use {@link #setPendingIntentCreatorBackgroundActivityStartMode(int)} instead
- */
- @Deprecated
- public ActivityOptions setIgnorePendingIntentCreatorForegroundState(boolean ignore) {
- mPendingIntentCreatorBackgroundActivityStartMode = ignore
- ? MODE_BACKGROUND_ACTIVITY_START_DENIED : MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
- return this;
- }
-
- /**
* Allow a {@link PendingIntent} to use the privilege of its creator to start background
* activities.
*
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3575545e202d..eaa23b9db166 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2608,7 +2608,14 @@ public final class ActivityThread extends ClientTransactionHandler
break;
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
- mTransactionExecutor.execute(transaction);
+ final ClientTransactionListenerController controller =
+ ClientTransactionListenerController.getInstance();
+ controller.onClientTransactionStarted();
+ try {
+ mTransactionExecutor.execute(transaction);
+ } finally {
+ controller.onClientTransactionFinished();
+ }
if (isSystem()) {
// Client transactions inside system process are recycled on the client side
// instead of ClientLifecycleManager to avoid being cleared before this
@@ -3720,12 +3727,6 @@ public final class ActivityThread extends ClientTransactionHandler
return mActivities.get(token);
}
- @Nullable
- @Override
- public Context getWindowContext(@NonNull IBinder clientToken) {
- return WindowTokenClientController.getInstance().getWindowContext(clientToken);
- }
-
@VisibleForTesting(visibility = PACKAGE)
public Configuration getConfiguration() {
return mConfigurationController.getConfiguration();
@@ -6747,6 +6748,21 @@ public final class ActivityThread extends ClientTransactionHandler
void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
@NonNull Configuration overrideConfig, int displayId,
@NonNull ActivityWindowInfo activityWindowInfo, boolean alwaysReportChange) {
+ final ClientTransactionListenerController controller =
+ ClientTransactionListenerController.getInstance();
+ final Context contextToUpdate = r.activity;
+ controller.onContextConfigurationPreChanged(contextToUpdate);
+ try {
+ handleActivityConfigurationChangedInner(r, overrideConfig, displayId,
+ activityWindowInfo, alwaysReportChange);
+ } finally {
+ controller.onContextConfigurationPostChanged(contextToUpdate);
+ }
+ }
+
+ private void handleActivityConfigurationChangedInner(@NonNull ActivityClientRecord r,
+ @NonNull Configuration overrideConfig, int displayId,
+ @NonNull ActivityWindowInfo activityWindowInfo, boolean alwaysReportChange) {
synchronized (mPendingOverrideConfigs) {
final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(r.token);
if (overrideConfig.isOtherSeqNewer(pendingOverrideConfig)) {
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 01153c9e7efc..f0c319673ade 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -23,7 +23,6 @@ import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.DestroyActivityItem;
import android.app.servertransaction.PendingTransactionActions;
import android.app.servertransaction.TransactionExecutor;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
@@ -32,7 +31,6 @@ import android.util.MergedConfiguration;
import android.view.SurfaceControl;
import android.window.ActivityWindowInfo;
import android.window.SplashScreenView.SplashScreenViewParcelable;
-import android.window.WindowContext;
import android.window.WindowContextInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -90,10 +88,6 @@ public abstract class ClientTransactionHandler {
/** Get activity instance for the token. */
public abstract Activity getActivity(IBinder token);
- /** Gets the {@link WindowContext} instance for the token. */
- @Nullable
- public abstract Context getWindowContext(@NonNull IBinder clientToken);
-
// Prepare phase related logic and handlers. Methods that inform about about pending changes or
// do other internal bookkeeping.
diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java
index 18dc1ce18baf..62a50dbbd6f7 100644
--- a/core/java/android/app/ConfigurationController.java
+++ b/core/java/android/app/ConfigurationController.java
@@ -21,6 +21,7 @@ import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.servertransaction.ClientTransactionListenerController;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.res.CompatibilityInfo;
@@ -145,6 +146,24 @@ class ConfigurationController {
*/
void handleConfigurationChanged(@Nullable Configuration config,
@Nullable CompatibilityInfo compat) {
+ final ClientTransactionListenerController controller =
+ ClientTransactionListenerController.getInstance();
+ final Context contextToUpdate = ActivityThread.currentApplication();
+ controller.onContextConfigurationPreChanged(contextToUpdate);
+ try {
+ handleConfigurationChangedInner(config, compat);
+ } finally {
+ controller.onContextConfigurationPostChanged(contextToUpdate);
+ }
+ }
+
+ /**
+ * Update the configuration to latest.
+ * @param config The new configuration.
+ * @param compat The new compatibility information.
+ */
+ private void handleConfigurationChangedInner(@Nullable Configuration config,
+ @Nullable CompatibilityInfo compat) {
int configDiff;
boolean equivalent;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ee0225fc8918..e3380e0bf12a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -21,12 +21,14 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.StrictMode.vmIncorrectContextUseEnabled;
import static android.view.WindowManager.LayoutParams.WindowType;
+import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UiContext;
+import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AttributionSource;
@@ -2288,7 +2290,35 @@ class ContextImpl extends Context {
Log.v(TAG, "Treating renounced permission " + permission + " as denied");
return PERMISSION_DENIED;
}
- return PermissionManager.checkPermission(permission, pid, uid, getDeviceId());
+
+ // When checking a device-aware permission on a remote device, if the permission is CAMERA
+ // or RECORD_AUDIO we need to check remote device's corresponding capability. If the remote
+ // device doesn't have capability fall back to checking permission on the default device.
+ // Note: we only perform permission check redirection when the device id is not explicitly
+ // set in the context.
+ int deviceId = getDeviceId();
+ if (deviceId != Context.DEVICE_ID_DEFAULT
+ && !mIsExplicitDeviceId
+ && PermissionManager.DEVICE_AWARE_PERMISSIONS.contains(permission)) {
+ VirtualDeviceManager virtualDeviceManager =
+ getSystemService(VirtualDeviceManager.class);
+ VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId);
+ if (virtualDevice != null) {
+ if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO)
+ && !virtualDevice.hasCustomAudioInputSupport())
+ || (Objects.equals(permission, Manifest.permission.CAMERA)
+ && !virtualDevice.hasCustomCameraSupport())) {
+ deviceId = Context.DEVICE_ID_DEFAULT;
+ }
+ } else {
+ Slog.e(
+ TAG,
+ "virtualDevice is not found when device id is not default. deviceId = "
+ + deviceId);
+ }
+ }
+
+ return PermissionManager.checkPermission(permission, pid, uid, deviceId);
}
/** @hide */
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index ffecd679d928..dca164d6acc1 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -452,12 +452,14 @@ interface IActivityManager {
in IBinder resultTo, in String resultWho, int requestCode, int flags,
in ProfilerInfo profilerInfo, in Bundle options, int userId);
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
- int stopUser(int userid, boolean force, in IStopUserCallback callback);
+ int stopUser(int userid, boolean stopProfileRegardlessOfParent, in IStopUserCallback callback);
+ int stopUserWithCallback(int userid, in IStopUserCallback callback);
+ int stopUserExceptCertainProfiles(int userid, boolean stopProfileRegardlessOfParent, in IStopUserCallback callback);
/**
- * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, boolean, IStopUserCallback)}
+ * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, IStopUserCallback)}
* for details.
*/
- int stopUserWithDelayedLocking(int userid, boolean force, in IStopUserCallback callback);
+ int stopUserWithDelayedLocking(int userid, in IStopUserCallback callback);
@UnsupportedAppUsage
void registerUserSwitchObserver(in IUserSwitchObserver observer, in String name);
@@ -499,6 +501,7 @@ interface IActivityManager {
in String shareDescription);
void requestInteractiveBugReport();
+ void requestBugReportWithExtraAttachment(in Uri extraAttachment);
void requestFullBugReport();
void requestRemoteBugReport(long nonce);
boolean launchBugReportHandlerApp();
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 8f81ae2ae7d6..cf0641651d66 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -50,8 +50,8 @@ interface INotificationManager
void cancelAllNotifications(String pkg, int userId);
void clearData(String pkg, int uid, boolean fromApp);
- void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, boolean isUiContext, int displayId, @nullable ITransientNotificationCallback callback);
- void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, boolean isUiContext, int displayId);
+ boolean enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, boolean isUiContext, int displayId, @nullable ITransientNotificationCallback callback);
+ boolean enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, boolean isUiContext, int displayId);
void cancelToast(String pkg, IBinder token);
void finishToken(String pkg, IBinder token);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index fe261bee41d8..d9e0413e5ad5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -23,6 +23,7 @@ import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICO
import static android.app.admin.DevicePolicyResources.UNDEFINED;
import static android.graphics.drawable.Icon.TYPE_URI;
import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
+import static android.app.Flags.cleanUpSpansAndNewLines;
import static android.app.Flags.evenlyDividedCallStyleActionLayout;
import static android.app.Flags.updateRankingTime;
@@ -3124,9 +3125,29 @@ public class Notification implements Parcelable
+ " instance is a custom Parcelable and not allowed in Notification");
return cs.toString();
}
+ if (Flags.cleanUpSpansAndNewLines()) {
+ return stripStyling(cs);
+ }
+
return removeTextSizeSpans(cs);
}
+ private static CharSequence stripStyling(@Nullable CharSequence cs) {
+ if (cs == null) {
+ return cs;
+ }
+
+ return cs.toString();
+ }
+
+ private static CharSequence cleanUpNewLines(@Nullable CharSequence charSequence) {
+ if (charSequence == null) {
+ return charSequence;
+ }
+
+ return charSequence.toString().replaceAll("[\r\n]+", "\n");
+ }
+
private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
if (charSequence instanceof Spanned) {
Spanned ss = (Spanned) charSequence;
@@ -5505,7 +5526,8 @@ public class Notification implements Parcelable
boolean hasSecondLine = showProgress;
if (p.hasTitle()) {
contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE);
- contentView.setTextViewText(p.mTitleViewId, ensureColorSpanContrast(p.mTitle, p));
+ contentView.setTextViewText(p.mTitleViewId,
+ ensureColorSpanContrastOrStripStyling(p.mTitle, p));
setTextViewColorPrimary(contentView, p.mTitleViewId, p);
} else if (p.mTitleViewId != R.id.title) {
// This alternate title view ID is not cleared by resetStandardTemplate
@@ -5515,7 +5537,8 @@ public class Notification implements Parcelable
if (p.mText != null && p.mText.length() != 0
&& (!showProgress || p.mAllowTextWithProgress)) {
contentView.setViewVisibility(p.mTextViewId, View.VISIBLE);
- contentView.setTextViewText(p.mTextViewId, ensureColorSpanContrast(p.mText, p));
+ contentView.setTextViewText(p.mTextViewId,
+ ensureColorSpanContrastOrStripStyling(p.mText, p));
setTextViewColorSecondary(contentView, p.mTextViewId, p);
hasSecondLine = true;
} else if (p.mTextViewId != R.id.text) {
@@ -5804,7 +5827,7 @@ public class Notification implements Parcelable
headerText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
}
if (!TextUtils.isEmpty(headerText)) {
- contentView.setTextViewText(R.id.header_text, ensureColorSpanContrast(
+ contentView.setTextViewText(R.id.header_text, ensureColorSpanContrastOrStripStyling(
processLegacyText(headerText), p));
setTextViewColorSecondary(contentView, R.id.header_text, p);
contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
@@ -5826,8 +5849,9 @@ public class Notification implements Parcelable
return false;
}
if (!TextUtils.isEmpty(p.mHeaderTextSecondary)) {
- contentView.setTextViewText(R.id.header_text_secondary, ensureColorSpanContrast(
- processLegacyText(p.mHeaderTextSecondary), p));
+ contentView.setTextViewText(R.id.header_text_secondary,
+ ensureColorSpanContrastOrStripStyling(
+ processLegacyText(p.mHeaderTextSecondary), p));
setTextViewColorSecondary(contentView, R.id.header_text_secondary, p);
contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE);
if (hasTextToLeft) {
@@ -6048,7 +6072,7 @@ public class Notification implements Parcelable
big.setViewVisibility(R.id.notification_material_reply_text_1_container,
View.VISIBLE);
big.setTextViewText(R.id.notification_material_reply_text_1,
- ensureColorSpanContrast(replyText[0].getText(), p));
+ ensureColorSpanContrastOrStripStyling(replyText[0].getText(), p));
setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p);
big.setViewVisibility(R.id.notification_material_reply_progress,
showSpinner ? View.VISIBLE : View.GONE);
@@ -6060,7 +6084,7 @@ public class Notification implements Parcelable
&& p.maxRemoteInputHistory > 1) {
big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
big.setTextViewText(R.id.notification_material_reply_text_2,
- ensureColorSpanContrast(replyText[1].getText(), p));
+ ensureColorSpanContrastOrStripStyling(replyText[1].getText(), p));
setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p);
if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText())
@@ -6068,7 +6092,7 @@ public class Notification implements Parcelable
big.setViewVisibility(
R.id.notification_material_reply_text_3, View.VISIBLE);
big.setTextViewText(R.id.notification_material_reply_text_3,
- ensureColorSpanContrast(replyText[2].getText(), p));
+ ensureColorSpanContrastOrStripStyling(replyText[2].getText(), p));
setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p);
}
}
@@ -6500,21 +6524,37 @@ public class Notification implements Parcelable
mContext, getColors(p).getBackgroundColor(), mInNightMode),
R.dimen.notification_action_disabled_container_alpha);
}
- if (isLegacy()) {
- title = ContrastColorUtil.clearColorSpans(title);
+ if (Flags.cleanUpSpansAndNewLines()) {
+ if (!isLegacy()) {
+ // Check for a full-length span color to use as the button fill color.
+ Integer fullLengthColor = getFullLengthSpanColor(title);
+ if (fullLengthColor != null) {
+ // Ensure the custom button fill has 1.3:1 contrast w/ notification bg.
+ int notifBackgroundColor = getColors(p).getBackgroundColor();
+ buttonFillColor = ensureButtonFillContrast(
+ fullLengthColor, notifBackgroundColor);
+ }
+ }
} else {
- // Check for a full-length span color to use as the button fill color.
- Integer fullLengthColor = getFullLengthSpanColor(title);
- if (fullLengthColor != null) {
- // Ensure the custom button fill has 1.3:1 contrast w/ notification bg.
- int notifBackgroundColor = getColors(p).getBackgroundColor();
- buttonFillColor = ensureButtonFillContrast(
- fullLengthColor, notifBackgroundColor);
+ if (isLegacy()) {
+ title = ContrastColorUtil.clearColorSpans(title);
+ } else {
+ // Check for a full-length span color to use as the button fill color.
+ Integer fullLengthColor = getFullLengthSpanColor(title);
+ if (fullLengthColor != null) {
+ // Ensure the custom button fill has 1.3:1 contrast w/ notification bg.
+ int notifBackgroundColor = getColors(p).getBackgroundColor();
+ buttonFillColor = ensureButtonFillContrast(
+ fullLengthColor, notifBackgroundColor);
+ }
+ // Remove full-length color spans
+ // and ensure text contrast with the button fill.
+ title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
}
- // Remove full-length color spans and ensure text contrast with the button fill.
- title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
}
- final CharSequence label = ensureColorSpanContrast(title, p);
+
+
+ final CharSequence label = ensureColorSpanContrastOrStripStyling(title, p);
if (p.mCallStyleActions && evenlyDividedCallStyleActionLayout()) {
if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
Log.d(TAG, "new action layout enabled, gluing instead of setting text");
@@ -6554,7 +6594,7 @@ public class Notification implements Parcelable
button.setIntDimen(R.id.action0, "setMinimumWidth", minWidthDimen);
}
} else {
- button.setTextViewText(R.id.action0, ensureColorSpanContrast(
+ button.setTextViewText(R.id.action0, ensureColorSpanContrastOrStripStyling(
action.title, p));
button.setTextColor(R.id.action0, getStandardActionColor(p));
}
@@ -6629,6 +6669,26 @@ public class Notification implements Parcelable
}
/**
+ * @hide
+ */
+ public CharSequence ensureColorSpanContrastOrStripStyling(CharSequence cs,
+ StandardTemplateParams p) {
+ return ensureColorSpanContrastOrStripStyling(cs, getBackgroundColor(p));
+ }
+
+ /**
+ * @hide
+ */
+ public CharSequence ensureColorSpanContrastOrStripStyling(CharSequence cs,
+ int buttonFillColor) {
+ if (Flags.cleanUpSpansAndNewLines()) {
+ return stripStyling(cs);
+ }
+
+ return ContrastColorUtil.ensureColorSpanContrast(cs, buttonFillColor);
+ }
+
+ /**
* Ensures contrast on color spans against a background color.
* Note that any full-length color spans will be removed instead of being contrasted.
*
@@ -7853,8 +7913,9 @@ public class Notification implements Parcelable
RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(),
p, null /* result */);
if (mSummaryTextSet) {
- contentView.setTextViewText(R.id.text, mBuilder.ensureColorSpanContrast(
- mBuilder.processLegacyText(mSummaryText), p));
+ contentView.setTextViewText(R.id.text,
+ mBuilder.ensureColorSpanContrastOrStripStyling(
+ mBuilder.processLegacyText(mSummaryText), p));
mBuilder.setTextViewColorSecondary(contentView, R.id.text, p);
contentView.setViewVisibility(R.id.text, View.VISIBLE);
}
@@ -8017,6 +8078,9 @@ public class Notification implements Parcelable
*/
public BigTextStyle bigText(CharSequence cs) {
mBigText = safeCharSequence(cs);
+ if (Flags.cleanUpSpansAndNewLines()) {
+ mBigText = cleanUpNewLines(mBigText);
+ }
return this;
}
@@ -8517,7 +8581,7 @@ public class Notification implements Parcelable
for (int i = 0; i < N; i++) {
final Message m = messages.get(i);
if (ensureContrast) {
- m.ensureColorContrast(backgroundColor);
+ m.ensureColorContrastOrStripStyling(backgroundColor);
}
bundles[i] = m.toBundle();
}
@@ -8543,7 +8607,9 @@ public class Notification implements Parcelable
} else {
title = sender;
}
-
+ if (Flags.cleanUpSpansAndNewLines()) {
+ title = stripStyling(title);
+ }
if (title != null) {
extras.putCharSequence(EXTRA_TITLE, title);
}
@@ -8995,6 +9061,17 @@ public class Notification implements Parcelable
}
/**
+ * Strip styling or updates TextAppearance spans in message text.
+ * @hide
+ */
+ public void ensureColorContrastOrStripStyling(int backgroundColor) {
+ if (Flags.cleanUpSpansAndNewLines()) {
+ mText = stripStyling(mText);
+ } else {
+ ensureColorContrast(backgroundColor);
+ }
+ }
+ /**
* Updates TextAppearance spans in the message text so it has sufficient contrast
* against its background.
* @hide
@@ -9324,7 +9401,8 @@ public class Notification implements Parcelable
if (!TextUtils.isEmpty(str)) {
contentView.setViewVisibility(rowIds[i], View.VISIBLE);
contentView.setTextViewText(rowIds[i],
- mBuilder.ensureColorSpanContrast(mBuilder.processLegacyText(str), p));
+ mBuilder.ensureColorSpanContrastOrStripStyling(
+ mBuilder.processLegacyText(str), p));
mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p);
contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
if (first) {
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 02d694436bfe..257aff04a09f 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1603,8 +1603,18 @@ public class WallpaperManager {
@SetWallpaperFlags int which, boolean originalBitmap) {
checkExactlyOneWallpaperFlagSet(which);
try {
- return sGlobals.mService.getBitmapCrops(displaySizes, which, originalBitmap,
- mContext.getUserId());
+ List<Rect> result = sGlobals.mService.getBitmapCrops(
+ displaySizes, which, originalBitmap, mContext.getUserId());
+ if (result != null) return result;
+ // mService.getBitmapCrops returns null if the requested wallpaper is an ImageWallpaper,
+ // but there are no crop hints and the bitmap size is unknown to the service (this
+ // mostly happens for the default wallpaper). In that case, fetch the bitmap dimensions
+ // and use the other getBitmapCrops API with no cropHints to figure out the crops.
+ Rect bitmapDimensions = peekBitmapDimensions(which, true);
+ if (bitmapDimensions == null) return List.of();
+ Point bitmapSize = new Point(bitmapDimensions.width(), bitmapDimensions.height());
+ return getBitmapCrops(bitmapSize, displaySizes, null);
+
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index e4425ca1e8a5..e751bd28cff1 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -1,5 +1,4 @@
package: "android.app"
-container: "system"
flag {
namespace: "system_performance"
diff --git a/core/java/android/app/admin/AccountTypePolicyKey.java b/core/java/android/app/admin/AccountTypePolicyKey.java
index 51f313755e59..02e492bb06aa 100644
--- a/core/java/android/app/admin/AccountTypePolicyKey.java
+++ b/core/java/android/app/admin/AccountTypePolicyKey.java
@@ -54,7 +54,7 @@ public final class AccountTypePolicyKey extends PolicyKey {
@TestApi
public AccountTypePolicyKey(@NonNull String key, @NonNull String accountType) {
super(key);
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
PolicySizeVerifier.enforceMaxStringLength(accountType, "accountType");
}
mAccountType = Objects.requireNonNull((accountType));
diff --git a/core/java/android/app/admin/BundlePolicyValue.java b/core/java/android/app/admin/BundlePolicyValue.java
index cb5e9861141d..c993671f4fc1 100644
--- a/core/java/android/app/admin/BundlePolicyValue.java
+++ b/core/java/android/app/admin/BundlePolicyValue.java
@@ -31,7 +31,7 @@ public final class BundlePolicyValue extends PolicyValue<Bundle> {
public BundlePolicyValue(Bundle value) {
super(value);
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
PolicySizeVerifier.enforceMaxBundleFieldsLength(value);
}
}
diff --git a/core/java/android/app/admin/ComponentNamePolicyValue.java b/core/java/android/app/admin/ComponentNamePolicyValue.java
index a957dbf132bb..a7a2f7d27e0d 100644
--- a/core/java/android/app/admin/ComponentNamePolicyValue.java
+++ b/core/java/android/app/admin/ComponentNamePolicyValue.java
@@ -31,7 +31,7 @@ public final class ComponentNamePolicyValue extends PolicyValue<ComponentName> {
public ComponentNamePolicyValue(@NonNull ComponentName value) {
super(value);
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
PolicySizeVerifier.enforceMaxComponentNameLength(value);
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ea6f45e8e201..ba91be9e9e6c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -54,6 +54,7 @@ import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
import static android.Manifest.permission.SET_TIME;
import static android.Manifest.permission.SET_TIME_ZONE;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
@@ -7055,9 +7056,10 @@ public class DevicePolicyManager {
public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0;
/**
- * Disable all keyguard widgets. Has no effect starting from
- * {@link android.os.Build.VERSION_CODES#LOLLIPOP} since keyguard widget is only supported
- * on Android versions lower than 5.0.
+ * Disable all keyguard widgets. Has no effect between {@link
+ * android.os.Build.VERSION_CODES#LOLLIPOP} and {@link
+ * android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} (both inclusive), since keyguard widget is
+ * only supported on Android versions lower than 5.0 and versions higher than 14.
*/
public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1 << 0;
@@ -7156,7 +7158,8 @@ public class DevicePolicyManager {
public static final int ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY =
DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA
| DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS
- | DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL;
+ | DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
+ | DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL;
/**
* Keyguard features that when set on a normal or organization-owned managed profile, have
@@ -8977,6 +8980,10 @@ public class DevicePolicyManager {
* by applications in the managed profile.
* </ul>
* <p>
+ * From version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, the profile owner of a
+ * managed profile can also set {@link #KEYGUARD_DISABLE_WIDGETS_ALL} which disables keyguard
+ * widgets for the managed profile.
+ * <p>
* From version {@link android.os.Build.VERSION_CODES#R} the profile owner of an
* organization-owned managed profile can set:
* <ul>
@@ -8985,6 +8992,12 @@ public class DevicePolicyManager {
* <li>{@link #KEYGUARD_DISABLE_SECURE_NOTIFICATIONS} which affects the parent user when called
* on the parent profile.
* </ul>
+ * Starting from version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} the profile
+ * owner of an organization-owned managed profile can set:
+ * <ul>
+ * <li>{@link #KEYGUARD_DISABLE_WIDGETS_ALL} which affects the parent user when called on the
+ * parent profile.
+ * </ul>
* {@link #KEYGUARD_DISABLE_TRUST_AGENTS}, {@link #KEYGUARD_DISABLE_FINGERPRINT},
* {@link #KEYGUARD_DISABLE_FACE}, {@link #KEYGUARD_DISABLE_IRIS},
* {@link #KEYGUARD_DISABLE_SECURE_CAMERA} and {@link #KEYGUARD_DISABLE_SECURE_NOTIFICATIONS}
@@ -17560,6 +17573,48 @@ public class DevicePolicyManager {
}
/**
+ * Force sets the maximum storage size allowed for policies associated with an admin regardless
+ * of the default value set in the system, unlike {@link #setMaxPolicyStorageLimit} which can
+ * only set it to a value higher than the default value set by the system.Setting a limit of -1
+ * effectively removes any storage restrictions.
+ *
+ * @param storageLimit Maximum storage allowed in bytes. Use -1 to disable limits.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT)
+ @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED)
+ public void forceSetMaxPolicyStorageLimit(int storageLimit) {
+ if (mService != null) {
+ try {
+ mService.forceSetMaxPolicyStorageLimit(mContext.getPackageName(), storageLimit);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Retrieves the size of the current policies set by the {@code admin}.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT)
+ @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED)
+ public int getPolicySizeForAdmin(@NonNull EnforcingAdmin admin) {
+ if (mService != null) {
+ try {
+ return mService.getPolicySizeForAdmin(mContext.getPackageName(), admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return -1;
+ }
+
+ /**
* @return The headless device owner mode for the current set DO, returns
* {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED} if no DO is set.
*
diff --git a/core/java/android/app/admin/EnforcingAdmin.java b/core/java/android/app/admin/EnforcingAdmin.java
index 7c718f6651a2..f70a53f61671 100644
--- a/core/java/android/app/admin/EnforcingAdmin.java
+++ b/core/java/android/app/admin/EnforcingAdmin.java
@@ -16,9 +16,13 @@
package android.app.admin;
+import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
@@ -60,6 +64,8 @@ public final class EnforcingAdmin implements Parcelable {
*
* @hide
*/
+ @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED)
+ @TestApi
public EnforcingAdmin(
@NonNull String packageName, @NonNull Authority authority,
@NonNull UserHandle userHandle, @Nullable ComponentName componentName) {
@@ -101,6 +107,16 @@ public final class EnforcingAdmin implements Parcelable {
return mUserHandle;
}
+ /**
+ * Returns the {@link ComponentName} of the admin if applicable.
+ *
+ * @hide
+ */
+ @Nullable
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 2002326d76bd..d1837132e1a4 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -623,8 +623,10 @@ interface IDevicePolicyManager {
int[] getSubscriptionIds(String callerPackageName);
- void setMaxPolicyStorageLimit(String packageName, int storageLimit);
- int getMaxPolicyStorageLimit(String packageName);
+ void setMaxPolicyStorageLimit(String callerPackageName, int storageLimit);
+ void forceSetMaxPolicyStorageLimit(String callerPackageName, int storageLimit);
+ int getMaxPolicyStorageLimit(String callerPackageName);
+ int getPolicySizeForAdmin(String callerPackageName, in EnforcingAdmin admin);
int getHeadlessDeviceOwnerMode(String callerPackageName);
}
diff --git a/core/java/android/app/admin/LockTaskPolicy.java b/core/java/android/app/admin/LockTaskPolicy.java
index a36ea0508a95..68b4ad84d81a 100644
--- a/core/java/android/app/admin/LockTaskPolicy.java
+++ b/core/java/android/app/admin/LockTaskPolicy.java
@@ -135,7 +135,7 @@ public final class LockTaskPolicy extends PolicyValue<LockTaskPolicy> {
}
private void setPackagesInternal(Set<String> packages) {
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
for (String p : packages) {
PolicySizeVerifier.enforceMaxPackageNameLength(p);
}
diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java
index 389585f036db..1a04f6c908bc 100644
--- a/core/java/android/app/admin/PackagePermissionPolicyKey.java
+++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java
@@ -59,7 +59,7 @@ public final class PackagePermissionPolicyKey extends PolicyKey {
public PackagePermissionPolicyKey(@NonNull String identifier, @NonNull String packageName,
@NonNull String permissionName) {
super(identifier);
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
PolicySizeVerifier.enforceMaxStringLength(permissionName, "permissionName");
}
diff --git a/core/java/android/app/admin/PackagePolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java
index 68dc797f6513..9e31a23aec91 100644
--- a/core/java/android/app/admin/PackagePolicyKey.java
+++ b/core/java/android/app/admin/PackagePolicyKey.java
@@ -55,7 +55,7 @@ public final class PackagePolicyKey extends PolicyKey {
@TestApi
public PackagePolicyKey(@NonNull String key, @NonNull String packageName) {
super(key);
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
}
mPackageName = Objects.requireNonNull((packageName));
diff --git a/core/java/android/app/admin/Provisioning_OWNERS b/core/java/android/app/admin/Provisioning_OWNERS
index 8f71fc0c4c05..91b9761f6e71 100644
--- a/core/java/android/app/admin/Provisioning_OWNERS
+++ b/core/java/android/app/admin/Provisioning_OWNERS
@@ -1,4 +1,4 @@
# Assign bugs to android-enterprise-triage@google.com
ae-provisioning-reviews@google.com
-petuska@google.com #{LAST_RESORT_SUGGESTION}
+acjohnston@google.com #{LAST_RESORT_SUGGESTION}
file:EnterprisePlatform_OWNERS
diff --git a/core/java/android/app/admin/StringPolicyValue.java b/core/java/android/app/admin/StringPolicyValue.java
index 8995c0f20de8..6efe9ad0dbed 100644
--- a/core/java/android/app/admin/StringPolicyValue.java
+++ b/core/java/android/app/admin/StringPolicyValue.java
@@ -30,7 +30,7 @@ public final class StringPolicyValue extends PolicyValue<String> {
public StringPolicyValue(@NonNull String value) {
super(value);
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
PolicySizeVerifier.enforceMaxStringLength(value, "policyValue");
}
}
diff --git a/core/java/android/app/admin/StringSetPolicyValue.java b/core/java/android/app/admin/StringSetPolicyValue.java
index f37dfee0f9dc..12b11f4ba687 100644
--- a/core/java/android/app/admin/StringSetPolicyValue.java
+++ b/core/java/android/app/admin/StringSetPolicyValue.java
@@ -32,7 +32,7 @@ public final class StringSetPolicyValue extends PolicyValue<Set<String>> {
public StringSetPolicyValue(@NonNull Set<String> value) {
super(value);
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
for (String str : value) {
PolicySizeVerifier.enforceMaxStringLength(str, "policyValue");
}
diff --git a/core/java/android/app/admin/UserRestrictionPolicyKey.java b/core/java/android/app/admin/UserRestrictionPolicyKey.java
index ee90ccd9417f..9054287cb7a0 100644
--- a/core/java/android/app/admin/UserRestrictionPolicyKey.java
+++ b/core/java/android/app/admin/UserRestrictionPolicyKey.java
@@ -45,7 +45,7 @@ public final class UserRestrictionPolicyKey extends PolicyKey {
@TestApi
public UserRestrictionPolicyKey(@NonNull String identifier, @NonNull String restriction) {
super(identifier);
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
PolicySizeVerifier.enforceMaxStringLength(restriction, "restriction");
}
mRestriction = Objects.requireNonNull(restriction);
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 56fb4aa45fb3..6a07484eebc6 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -2,7 +2,6 @@
# proto-message: flag_declarations
package: "android.app.admin.flags"
-container: "system"
flag {
name: "policy_engine_migration_v2_enabled"
@@ -28,6 +27,17 @@ flag {
}
flag {
+ name: "device_policy_size_tracking_internal_bug_fix_enabled"
+ namespace: "enterprise"
+ description: "Bug fix for tracking the total policy size and have a max threshold"
+ bug: "281543351"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+
+flag {
name: "onboarding_bugreport_v2_enabled"
is_exported: true
namespace: "enterprise"
@@ -233,3 +243,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "headless_single_user_bad_device_admin_state_fix"
+ namespace: "enterprise"
+ description: "Fix the bad state in DPMS caused by an earlier bug related to the headless single user change"
+ bug: "332477138"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/background_install_control_manager.aconfig b/core/java/android/app/background_install_control_manager.aconfig
index d29c5b58092d..5f3bb0745b08 100644
--- a/core/java/android/app/background_install_control_manager.aconfig
+++ b/core/java/android/app/background_install_control_manager.aconfig
@@ -1,5 +1,4 @@
package: "android.app"
-container: "system"
flag {
namespace: "preload_safety"
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index 3385b2b42074..5ab07620bc81 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.app.contextualsearch.flags"
-container: "system"
flag {
name: "enable_service"
diff --git a/core/java/android/app/grammatical_inflection_manager.aconfig b/core/java/android/app/grammatical_inflection_manager.aconfig
index ea494f45c48c..0d7bf65215a0 100644
--- a/core/java/android/app/grammatical_inflection_manager.aconfig
+++ b/core/java/android/app/grammatical_inflection_manager.aconfig
@@ -1,5 +1,4 @@
package: "android.app"
-container: "system"
flag {
name: "system_terms_of_address_enabled"
diff --git a/core/java/android/app/multitasking.aconfig b/core/java/android/app/multitasking.aconfig
index 9a645192a155..dbf3173a4ee6 100644
--- a/core/java/android/app/multitasking.aconfig
+++ b/core/java/android/app/multitasking.aconfig
@@ -1,5 +1,4 @@
package: "android.app"
-container: "system"
flag {
name: "enable_pip_ui_state_callback_on_entering"
diff --git a/core/java/android/app/network-policy.aconfig b/core/java/android/app/network-policy.aconfig
index e7b02a76ef68..88f386f6025d 100644
--- a/core/java/android/app/network-policy.aconfig
+++ b/core/java/android/app/network-policy.aconfig
@@ -1,5 +1,4 @@
package: "android.app"
-container: "system"
flag {
namespace: "backstage_power"
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 00827321e18f..1f6ac2efd64f 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -1,5 +1,4 @@
package: "android.app"
-container: "system"
flag {
name: "modes_api"
@@ -110,4 +109,11 @@ flag {
namespace: "systemui"
description: "No notifs can use USAGE_UNKNOWN or USAGE_MEDIA"
bug: "331793339"
+}
+
+flag {
+ name: "clean_up_spans_and_new_lines"
+ namespace: "systemui"
+ description: "Cleans up spans and unnecessary new lines from standard notification templates"
+ bug: "313439845"
} \ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
index 8b6441ae5a7c..dd9210faa10c 100644
--- a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
+++ b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
@@ -1,5 +1,4 @@
package: "android.app.ondeviceintelligence.flags"
-container: "system"
flag {
name: "enable_on_device_intelligence"
diff --git a/core/java/android/app/pinner-client.aconfig b/core/java/android/app/pinner-client.aconfig
index 696fd3859184..0f7fa14d9b6a 100644
--- a/core/java/android/app/pinner-client.aconfig
+++ b/core/java/android/app/pinner-client.aconfig
@@ -1,5 +1,4 @@
package: "android.app"
-container: "system"
flag {
namespace: "system_performance"
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index 631772556879..11d7ff86ce3d 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -23,7 +23,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
-import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
@@ -60,12 +59,6 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem {
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
- @Nullable
- @Override
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- return client.getActivity(getActivityToken());
- }
-
// ObjectPoolItem implementation
private ActivityConfigurationChangeItem() {}
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index 6da871a74383..45bf235de2cd 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -23,7 +23,6 @@ import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.app.ResultInfo;
-import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.os.IBinder;
import android.os.Parcel;
@@ -88,12 +87,6 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
client.reportRelaunch(r);
}
- @Nullable
- @Override
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- return client.getActivity(getActivityToken());
- }
-
// ObjectPoolItem implementation
private ActivityRelaunchItem() {}
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index a8d61db1ce3a..99ebe1b975a4 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -24,7 +24,6 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ClientTransactionHandler;
-import android.content.Context;
import android.os.IBinder;
import android.os.Parcelable;
@@ -54,15 +53,6 @@ public abstract class ClientTransactionItem implements BaseClientRequest, Parcel
}
/**
- * If this {@link ClientTransactionItem} is updating configuration, returns the {@link Context}
- * it is updating; otherwise, returns {@code null}.
- */
- @Nullable
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- return null;
- }
-
- /**
* Returns the activity token if this transaction item is activity-targeting. Otherwise,
* returns {@code null}.
*/
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index c55b0f110b3b..722d5f0fe462 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -16,6 +16,8 @@
package android.app.servertransaction;
+import static android.app.WindowConfiguration.areConfigurationsEqualForDisplay;
+
import static com.android.window.flags.Flags.activityWindowInfoFlag;
import static com.android.window.flags.Flags.bundleClientTransactionFlag;
@@ -24,8 +26,11 @@ import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityThread;
+import android.content.Context;
+import android.content.res.Configuration;
import android.hardware.display.DisplayManagerGlobal;
import android.os.IBinder;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.window.ActivityWindowInfo;
@@ -51,6 +56,15 @@ public class ClientTransactionListenerController {
private final ArraySet<BiConsumer<IBinder, ActivityWindowInfo>>
mActivityWindowInfoChangedListeners = new ArraySet<>();
+ /**
+ * Keeps track of the Context whose Configuration will get updated, mapping to the config before
+ * the change.
+ */
+ private final ArrayMap<Context, Configuration> mContextToPreChangedConfigMap = new ArrayMap<>();
+
+ /** Whether there is an {@link ClientTransaction} being executed. */
+ private boolean mIsClientTransactionExecuting;
+
/** Gets the singleton controller. */
@NonNull
public static ClientTransactionListenerController getInstance() {
@@ -126,18 +140,92 @@ public class ClientTransactionListenerController {
}
}
- /**
- * Called when receives a {@link ClientTransaction} that is updating display-related
- * window configuration.
- */
- public void onDisplayChanged(int displayId) {
- if (!bundleClientTransactionFlag()) {
+ /** Called when starts executing a remote {@link ClientTransaction}. */
+ public void onClientTransactionStarted() {
+ mIsClientTransactionExecuting = true;
+ }
+
+ /** Called when finishes executing a remote {@link ClientTransaction}. */
+ public void onClientTransactionFinished() {
+ notifyDisplayManagerIfNeeded();
+ mIsClientTransactionExecuting = false;
+ }
+
+ /** Called before updating the Configuration of the given {@code context}. */
+ public void onContextConfigurationPreChanged(@NonNull Context context) {
+ if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) {
+ // Not enable for system server.
+ return;
+ }
+ if (mContextToPreChangedConfigMap.containsKey(context)) {
+ // There is an earlier change that hasn't been reported yet.
return;
}
- if (ActivityThread.isSystem()) {
+ mContextToPreChangedConfigMap.put(context,
+ new Configuration(context.getResources().getConfiguration()));
+ }
+
+ /** Called after updating the Configuration of the given {@code context}. */
+ public void onContextConfigurationPostChanged(@NonNull Context context) {
+ if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) {
// Not enable for system server.
return;
}
+ if (mIsClientTransactionExecuting) {
+ // Wait until #onClientTransactionFinished to prevent it from triggering the same
+ // #onDisplayChanged multiple times within the same ClientTransaction.
+ return;
+ }
+ final Configuration preChangedConfig = mContextToPreChangedConfigMap.remove(context);
+ if (preChangedConfig != null && shouldReportDisplayChange(context, preChangedConfig)) {
+ onDisplayChanged(context.getDisplayId());
+ }
+ }
+
+ /**
+ * When {@link Configuration} is changed, we want to trigger display change callback as well,
+ * because Display reads some fields from {@link Configuration}.
+ */
+ private void notifyDisplayManagerIfNeeded() {
+ if (mContextToPreChangedConfigMap.isEmpty()) {
+ return;
+ }
+ // Whether the configuration change should trigger DisplayListener#onDisplayChanged.
+ try {
+ // Calculate display ids that have config changed.
+ final ArraySet<Integer> configUpdatedDisplayIds = new ArraySet<>();
+ final int contextCount = mContextToPreChangedConfigMap.size();
+ for (int i = 0; i < contextCount; i++) {
+ final Context context = mContextToPreChangedConfigMap.keyAt(i);
+ final Configuration preChangedConfig = mContextToPreChangedConfigMap.valueAt(i);
+ if (shouldReportDisplayChange(context, preChangedConfig)) {
+ configUpdatedDisplayIds.add(context.getDisplayId());
+ }
+ }
+
+ // Dispatch the display changed callbacks.
+ final int displayCount = configUpdatedDisplayIds.size();
+ for (int i = 0; i < displayCount; i++) {
+ final int displayId = configUpdatedDisplayIds.valueAt(i);
+ onDisplayChanged(displayId);
+ }
+ } finally {
+ mContextToPreChangedConfigMap.clear();
+ }
+ }
+
+ private boolean shouldReportDisplayChange(@NonNull Context context,
+ @NonNull Configuration preChangedConfig) {
+ final Configuration postChangedConfig = context.getResources().getConfiguration();
+ return !areConfigurationsEqualForDisplay(postChangedConfig, preChangedConfig);
+ }
+
+ /**
+ * Called when receives a {@link Configuration} changed event that is updating display-related
+ * window configuration.
+ */
+ @VisibleForTesting
+ public void onDisplayChanged(int displayId) {
mDisplayManager.handleDisplayChangeFromWindowManager(displayId);
}
}
diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
index 0e327a7627d1..22da706cc7f4 100644
--- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
@@ -18,9 +18,7 @@ package android.app.servertransaction;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityThread;
import android.app.ClientTransactionHandler;
-import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.Parcel;
@@ -48,12 +46,6 @@ public class ConfigurationChangeItem extends ClientTransactionItem {
client.handleConfigurationChanged(mConfiguration, mDeviceId);
}
- @Nullable
- @Override
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- return ActivityThread.currentApplication();
- }
-
// ObjectPoolItem implementation
private ConfigurationChangeItem() {}
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index f02cb212276b..7dcbebaeba0b 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -24,14 +24,12 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityClient;
import android.app.ActivityOptions.SceneTransitionInfo;
-import android.app.ActivityThread;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.app.IActivityClientController;
import android.app.ProfilerInfo;
import android.app.ResultInfo;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
@@ -121,13 +119,6 @@ public class LaunchActivityItem extends ClientTransactionItem {
client.countLaunchingActivities(-1);
}
- @Nullable
- @Override
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- // LaunchActivityItem may update the global config with #mCurConfig.
- return ActivityThread.currentApplication();
- }
-
// ObjectPoolItem implementation
private LaunchActivityItem() {}
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index 0702c4594075..8706edd26406 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -22,7 +22,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
-import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
@@ -59,12 +58,6 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
- @Nullable
- @Override
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- return client.getActivity(getActivityToken());
- }
-
// ObjectPoolItem implementation
private MoveToDisplayItem() {}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index c83719149821..480205ebc756 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -16,7 +16,6 @@
package android.app.servertransaction;
-import static android.app.WindowConfiguration.areConfigurationsEqualForDisplay;
import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE;
import static android.app.servertransaction.ActivityLifecycleItem.ON_DESTROY;
import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
@@ -32,17 +31,12 @@ import static android.app.servertransaction.TransactionExecutorHelper.shouldExcl
import static android.app.servertransaction.TransactionExecutorHelper.tId;
import static android.app.servertransaction.TransactionExecutorHelper.transactionToString;
-import static com.android.window.flags.Flags.bundleClientTransactionFlag;
-
import android.annotation.NonNull;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.content.Context;
-import android.content.res.Configuration;
import android.os.IBinder;
import android.os.Trace;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
@@ -63,12 +57,6 @@ public class TransactionExecutor {
private final PendingTransactionActions mPendingActions = new PendingTransactionActions();
private final TransactionExecutorHelper mHelper = new TransactionExecutorHelper();
- /**
- * Keeps track of the Context whose Configuration got updated within a transaction, mapping to
- * the config before the transaction.
- */
- private final ArrayMap<Context, Configuration> mContextToPreChangedConfigMap = new ArrayMap<>();
-
/** Initialize an instance with transaction handler, that will execute all requested actions. */
public TransactionExecutor(@NonNull ClientTransactionHandler clientTransactionHandler) {
mTransactionHandler = clientTransactionHandler;
@@ -104,37 +92,6 @@ public class TransactionExecutor {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
- if (!mContextToPreChangedConfigMap.isEmpty()) {
- // Whether this transaction should trigger DisplayListener#onDisplayChanged.
- try {
- // Calculate display ids that have config changed.
- final ArraySet<Integer> configUpdatedDisplayIds = new ArraySet<>();
- final int contextCount = mContextToPreChangedConfigMap.size();
- for (int i = 0; i < contextCount; i++) {
- final Context context = mContextToPreChangedConfigMap.keyAt(i);
- final Configuration preTransactionConfig =
- mContextToPreChangedConfigMap.valueAt(i);
- final Configuration postTransactionConfig = context.getResources()
- .getConfiguration();
- if (!areConfigurationsEqualForDisplay(
- postTransactionConfig, preTransactionConfig)) {
- configUpdatedDisplayIds.add(context.getDisplayId());
- }
- }
-
- // Dispatch the display changed callbacks.
- final ClientTransactionListenerController controller =
- ClientTransactionListenerController.getInstance();
- final int displayCount = configUpdatedDisplayIds.size();
- for (int i = 0; i < displayCount; i++) {
- final int displayId = configUpdatedDisplayIds.valueAt(i);
- controller.onDisplayChanged(displayId);
- }
- } finally {
- mContextToPreChangedConfigMap.clear();
- }
- }
-
mPendingActions.clear();
if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction");
}
@@ -214,20 +171,6 @@ public class TransactionExecutor {
}
}
- final boolean shouldTrackConfigUpdatedContext =
- // No configuration change for local transaction.
- !mTransactionHandler.isExecutingLocalTransaction()
- && bundleClientTransactionFlag();
- final Context configUpdatedContext = shouldTrackConfigUpdatedContext
- ? item.getContextToUpdate(mTransactionHandler)
- : null;
- if (configUpdatedContext != null
- && !mContextToPreChangedConfigMap.containsKey(configUpdatedContext)) {
- // Keep track of the first pre-executed config of each changed Context.
- mContextToPreChangedConfigMap.put(configUpdatedContext,
- new Configuration(configUpdatedContext.getResources().getConfiguration()));
- }
-
item.execute(mTransactionHandler, mPendingActions);
item.postExecute(mTransactionHandler, mPendingActions);
diff --git a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
index cbad92ff3f38..f6a72915e639 100644
--- a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
+++ b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
@@ -21,7 +21,6 @@ import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ClientTransactionHandler;
-import android.content.Context;
import android.content.res.Configuration;
import android.os.IBinder;
import android.os.Parcel;
@@ -46,12 +45,6 @@ public class WindowContextInfoChangeItem extends ClientTransactionItem {
client.handleWindowContextInfoChanged(mClientToken, mInfo);
}
- @Nullable
- @Override
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- return client.getWindowContext(mClientToken);
- }
-
// ObjectPoolItem implementation
private WindowContextInfoChangeItem() {}
diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java
index 1817c5eefb14..da99096f022a 100644
--- a/core/java/android/app/servertransaction/WindowStateResizeItem.java
+++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java
@@ -22,10 +22,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityThread;
import android.app.ClientTransactionHandler;
-import android.content.Context;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.Trace;
@@ -59,10 +56,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
/** {@code null} if this is not an Activity window. */
@Nullable
- private IBinder mActivityToken;
-
- /** {@code null} if this is not an Activity window. */
- @Nullable
private ActivityWindowInfo mActivityWindowInfo;
@Override
@@ -86,14 +79,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
- @Nullable
- @Override
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- // TODO(b/260873529): dispatch for mActivityToken as well.
- // WindowStateResizeItem may update the global config with #mConfiguration.
- return ActivityThread.currentApplication();
- }
-
// ObjectPoolItem implementation
private WindowStateResizeItem() {}
@@ -103,8 +88,7 @@ public class WindowStateResizeItem extends ClientTransactionItem {
@NonNull ClientWindowFrames frames, boolean reportDraw,
@NonNull MergedConfiguration configuration, @NonNull InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
- boolean dragResizing, @Nullable IBinder activityToken,
- @Nullable ActivityWindowInfo activityWindowInfo) {
+ boolean dragResizing, @Nullable ActivityWindowInfo activityWindowInfo) {
WindowStateResizeItem instance =
ObjectPool.obtain(WindowStateResizeItem.class);
if (instance == null) {
@@ -120,7 +104,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
instance.mDisplayId = displayId;
instance.mSyncSeqId = syncSeqId;
instance.mDragResizing = dragResizing;
- instance.mActivityToken = activityToken;
instance.mActivityWindowInfo = activityWindowInfo != null
? new ActivityWindowInfo(activityWindowInfo)
: null;
@@ -140,7 +123,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
mDisplayId = INVALID_DISPLAY;
mSyncSeqId = -1;
mDragResizing = false;
- mActivityToken = null;
mActivityWindowInfo = null;
ObjectPool.recycle(this);
}
@@ -160,7 +142,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
dest.writeInt(mDisplayId);
dest.writeInt(mSyncSeqId);
dest.writeBoolean(mDragResizing);
- dest.writeStrongBinder(mActivityToken);
dest.writeTypedObject(mActivityWindowInfo, flags);
}
@@ -176,7 +157,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
mDisplayId = in.readInt();
mSyncSeqId = in.readInt();
mDragResizing = in.readBoolean();
- mActivityToken = in.readStrongBinder();
mActivityWindowInfo = in.readTypedObject(ActivityWindowInfo.CREATOR);
}
@@ -209,7 +189,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
&& mDisplayId == other.mDisplayId
&& mSyncSeqId == other.mSyncSeqId
&& mDragResizing == other.mDragResizing
- && Objects.equals(mActivityToken, other.mActivityToken)
&& Objects.equals(mActivityWindowInfo, other.mActivityWindowInfo);
}
@@ -226,7 +205,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
result = 31 * result + mDisplayId;
result = 31 * result + mSyncSeqId;
result = 31 * result + (mDragResizing ? 1 : 0);
- result = 31 * result + Objects.hashCode(mActivityToken);
result = 31 * result + Objects.hashCode(mActivityWindowInfo);
return result;
}
@@ -236,7 +214,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
return "WindowStateResizeItem{window=" + mWindow
+ ", reportDrawn=" + mReportDraw
+ ", configuration=" + mConfiguration
- + ", activityToken=" + mActivityToken
+ ", activityWindowInfo=" + mActivityWindowInfo
+ "}";
}
diff --git a/core/java/android/app/smartspace/flags.aconfig b/core/java/android/app/smartspace/flags.aconfig
index df7192426934..e90ba67fe6dd 100644
--- a/core/java/android/app/smartspace/flags.aconfig
+++ b/core/java/android/app/smartspace/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.app.smartspace.flags"
-container: "system"
flag {
name: "remote_views"
diff --git a/core/java/android/app/ui_mode_manager.aconfig b/core/java/android/app/ui_mode_manager.aconfig
index 9f44a4d5ee01..27a38cc2bfd6 100644
--- a/core/java/android/app/ui_mode_manager.aconfig
+++ b/core/java/android/app/ui_mode_manager.aconfig
@@ -1,5 +1,4 @@
package: "android.app"
-container: "system"
flag {
namespace: "systemui"
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index c7b168aaf81d..9a2d2e5d8319 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.app.usage"
-container: "system"
flag {
name: "user_interaction_type_api"
diff --git a/core/java/android/app/wearable/flags.aconfig b/core/java/android/app/wearable/flags.aconfig
index b68bafe279bf..d1d7b5d85e2d 100644
--- a/core/java/android/app/wearable/flags.aconfig
+++ b/core/java/android/app/wearable/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.app.wearable"
-container: "system"
flag {
name: "enable_unsupported_operation_status_code"
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 3bcc7c79da14..765c802c2df3 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.appwidget.flags"
-container: "system"
flag {
name: "generated_previews"
@@ -33,3 +32,10 @@ flag {
description: "Enable support for transporting draw instructions as data parcel"
bug: "286130467"
}
+
+flag {
+ name: "throttle_widget_updates"
+ namespace: "app_widgets"
+ description: "Throttle the widget view updates to mitigate transaction exceptions"
+ bug: "326145514"
+}
diff --git a/core/java/android/companion/ObservingDevicePresenceRequest.java b/core/java/android/companion/ObservingDevicePresenceRequest.java
index f1d594e80bda..11ea735dff4f 100644
--- a/core/java/android/companion/ObservingDevicePresenceRequest.java
+++ b/core/java/android/companion/ObservingDevicePresenceRequest.java
@@ -183,7 +183,11 @@ public final class ObservingDevicePresenceRequest implements Parcelable {
* @param uuid The ParcelUuid for observing device presence.
*/
@NonNull
- @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE,
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_SCAN
+ })
public Builder setUuid(@NonNull ParcelUuid uuid) {
checkNotUsed();
this.mUuid = uuid;
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 84588577b6d6..ecc5e1bd194f 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.companion"
-container: "system"
flag {
name: "new_association_builder"
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 18c81a2fe8f7..e6649df26158 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -8,7 +8,6 @@
# instead.
package: "android.companion.virtual.flags"
-container: "system"
flag {
name: "enable_native_vdm"
@@ -117,3 +116,14 @@ flag {
description: "Enable virtual stylus input"
bug: "304829446"
}
+
+flag {
+ name: "intercept_intents_before_applying_policy"
+ is_exported: true
+ namespace: "virtual_devices"
+ description: "Apply intent interception before applying activity policy"
+ bug: "333444131"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 006226eb8c31..2904e7c989e8 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -14,7 +14,6 @@
# limitations under the License.
package: "android.companion.virtualdevice.flags"
-container: "system"
flag {
namespace: "virtual_devices"
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b706cae17547..bad73fc68f3a 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3256,6 +3256,14 @@ public abstract class Context {
*
* <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
*
+ * <p>As of {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the system can <a
+ * href="{@docRoot}develop/background-work/background-tasks/broadcasts#android-14">place
+ * context-registered broadcasts in a queue while the app is in the <a
+ * href="{@docRoot}guide/components/activities/process-lifecycle">cached state</a>.
+ * When the app leaves the cached state, such as returning to the
+ * foreground, the system delivers any queued broadcasts. Multiple instances
+ * of certain broadcasts might be merged into one broadcast.
+ *
* <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers
* registered with this method will correctly respect the
* {@link Intent#setPackage(String)} specified for an Intent being broadcast.
@@ -3301,6 +3309,14 @@ public abstract class Context {
*
* <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
*
+ * <p>As of {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the system can <a
+ * href="{@docRoot}develop/background-work/background-tasks/broadcasts#android-14">place
+ * context-registered broadcasts in a queue while the app is in the <a
+ * href="{@docRoot}guide/components/activities/process-lifecycle">cached state</a>.
+ * When the app leaves the cached state, such as returning to the
+ * foreground, the system delivers any queued broadcasts. Multiple instances
+ * of certain broadcasts might be merged into one broadcast.
+ *
* <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers
* registered with this method will correctly respect the
* {@link Intent#setPackage(String)} specified for an Intent being broadcast.
@@ -3342,6 +3358,14 @@ public abstract class Context {
*
* <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
*
+ * <p>As of {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the system can <a
+ * href="{@docRoot}develop/background-work/background-tasks/broadcasts#android-14">place
+ * context-registered broadcasts in a queue while the app is in the <a
+ * href="{@docRoot}guide/components/activities/process-lifecycle">cached state</a>.
+ * When the app leaves the cached state, such as returning to the
+ * foreground, the system delivers any queued broadcasts. Multiple instances
+ * of certain broadcasts might be merged into one broadcast.
+ *
* <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers
* registered with this method will correctly respect the
* {@link Intent#setPackage(String)} specified for an Intent being broadcast.
@@ -3385,6 +3409,14 @@ public abstract class Context {
*
* <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
*
+ * <p>As of {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the system can <a
+ * href="{@docRoot}develop/background-work/background-tasks/broadcasts#android-14">place
+ * context-registered broadcasts in a queue while the app is in the <a
+ * href="{@docRoot}guide/components/activities/process-lifecycle">cached state</a>.
+ * When the app leaves the cached state, such as returning to the
+ * foreground, the system delivers any queued broadcasts. Multiple instances
+ * of certain broadcasts might be merged into one broadcast.
+ *
* <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers
* registered with this method will correctly respect the
* {@link Intent#setPackage(String)} specified for an Intent being broadcast.
diff --git a/core/java/android/content/flags/flags.aconfig b/core/java/android/content/flags/flags.aconfig
index aac04b3a9d15..27bce5bb83dd 100644
--- a/core/java/android/content/flags/flags.aconfig
+++ b/core/java/android/content/flags/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.content.flags"
-container: "system"
flag {
name: "enable_bind_package_isolated_process"
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 915992904a5c..1d4403cb79e8 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -887,7 +887,7 @@ public final class UserProperties implements Parcelable {
* <p> Setting this property to true will enable the user's CE storage to remain unlocked when
* the user is stopped using
* {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int,
- * boolean, IStopUserCallback)}.
+ * IStopUserCallback)}.
*
* <p> When this property is false, delayed locking may still be applicable at a global
* level for all users via the {@code config_multiuserDelayUserDataLocking}. That is, delayed
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 6158917cc7df..cde565b3f66e 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.content.pm"
-container: "system"
flag {
name: "quarantined_enabled"
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 0c0da3159774..4963a4f27803 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -1,5 +1,4 @@
package: "android.multiuser"
-container: "system"
flag {
name: "save_global_and_guest_restrictions_on_system_user_xml"
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index a475cc85e921..8f5c912d8c03 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.content.res"
-container: "system"
flag {
name: "default_locale"
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 93fa5d85796f..eb7afb8ea82c 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -61,7 +61,9 @@ import java.util.concurrent.Executor;
@SystemService(Context.CREDENTIAL_SERVICE)
@RequiresFeature(PackageManager.FEATURE_CREDENTIALS)
public final class CredentialManager {
- private static final String TAG = "CredentialManager";
+ /** @hide **/
+ @Hide
+ public static final String TAG = "CredentialManager";
private static final Bundle OPTIONS_SENDER_BAL_OPTIN = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle();
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index d2435757756c..d0773297a4a0 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.credentials.flags"
-container: "system"
flag {
namespace: "credential_manager"
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 3073e25d45b4..7ecffaf01549 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.database.sqlite"
-container: "system"
flag {
name: "sqlite_apis_35"
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 4284ad09e251..9836eece19fe 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.hardware.biometrics"
-container: "system"
flag {
name: "last_authentication_time"
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index dca663d206d3..50d976f683cb 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -1768,9 +1768,12 @@ public abstract class CameraDevice implements AutoCloseable {
* @param sessionConfig The session configuration for which characteristics are fetched.
* @return CameraCharacteristics specific to a given session configuration.
*
- * @throws IllegalArgumentException if the session configuration is invalid
- * @throws CameraAccessException if the camera device is no longer connected or has
- * encountered a fatal error
+ * @throws IllegalArgumentException if the session configuration is invalid or if
+ * {@link #isSessionConfigurationSupported} returns
+ * {@code false} for the provided
+ * {@link SessionConfiguration}
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
*
* @see CameraCharacteristics#getAvailableSessionCharacteristicsKeys
*/
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
index 81d0976c09bb..372839d837f9 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
@@ -104,8 +104,8 @@ public class CameraDeviceSetupImpl extends CameraDevice.CameraDeviceSetup {
}
try {
- return cameraService.isSessionConfigurationWithParametersSupported(
- mCameraId, config, mContext.getDeviceId(),
+ return cameraService.isSessionConfigurationWithParametersSupported(mCameraId,
+ mTargetSdkVersion, config, mContext.getDeviceId(),
mCameraManager.getDevicePolicyFromContext(mContext));
} catch (ServiceSpecificException e) {
throw ExceptionUtils.throwAsPublicException(e);
diff --git a/core/java/android/hardware/devicestate/feature/flags.aconfig b/core/java/android/hardware/devicestate/feature/flags.aconfig
index 12d3f94ec982..e474603f2b03 100644
--- a/core/java/android/hardware/devicestate/feature/flags.aconfig
+++ b/core/java/android/hardware/devicestate/feature/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.hardware.devicestate.feature.flags"
-container: "system"
flag {
name: "device_state_property_api"
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index ec67212b46b7..89ab105e8ce3 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -364,6 +364,14 @@ public abstract class DisplayManagerInternal {
public abstract List<RefreshRateLimitation> getRefreshRateLimitations(int displayId);
/**
+ * Returns if vrr support is enabled for specified display
+ *
+ * @param displayId The id of the display.
+ * @return true if associated display supports dvrr
+ */
+ public abstract boolean isVrrSupportEnabled(int displayId);
+
+ /**
* For the given displayId, updates if WindowManager is responsible for mirroring on that
* display. If {@code false}, then SurfaceFlinger performs no layer mirroring to the
* given display.
diff --git a/core/java/android/hardware/flags/overlayproperties_flags.aconfig b/core/java/android/hardware/flags/overlayproperties_flags.aconfig
index 6c86108c4034..1165e650f469 100644
--- a/core/java/android/hardware/flags/overlayproperties_flags.aconfig
+++ b/core/java/android/hardware/flags/overlayproperties_flags.aconfig
@@ -1,5 +1,4 @@
package: "android.hardware.flags"
-container: "system"
flag {
name: "overlayproperties_class_api"
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 2816f777e8ab..1c37aa25af5f 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -94,33 +94,8 @@ interface IInputManager {
// Keyboard layouts configuration.
KeyboardLayout[] getKeyboardLayouts();
- KeyboardLayout[] getKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
-
KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
- String getCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier);
-
- @EnforcePermission("SET_KEYBOARD_LAYOUT")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
- void setCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor);
-
- String[] getEnabledKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
-
- @EnforcePermission("SET_KEYBOARD_LAYOUT")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
- void addKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor);
-
- @EnforcePermission("SET_KEYBOARD_LAYOUT")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
- void removeKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor);
-
- // New Keyboard layout config APIs
KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
in InputDeviceIdentifier identifier, int userId, in InputMethodInfo imeInfo,
in InputMethodSubtype imeSubtype);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index a1242fb43bbd..f94915876a2f 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -473,22 +473,21 @@ public final class InputManager {
}
/**
- * Returns the descriptors of all supported keyboard layouts appropriate for the specified
- * input device.
+ * Returns the descriptors of all supported keyboard layouts.
* <p>
* The input manager consults the built-in keyboard layouts as well as all keyboard layouts
* advertised by applications using a {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
* </p>
*
- * @param device The input device to query.
* @return The ids of all keyboard layouts which are supported by the specified input device.
*
* @hide
*/
@TestApi
@NonNull
- public List<String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull InputDevice device) {
- KeyboardLayout[] layouts = getKeyboardLayoutsForInputDevice(device.getIdentifier());
+ @SuppressLint("UnflaggedApi")
+ public List<String> getKeyboardLayoutDescriptors() {
+ KeyboardLayout[] layouts = getKeyboardLayouts();
List<String> res = new ArrayList<>();
for (KeyboardLayout kl : layouts) {
res.add(kl.getDescriptor());
@@ -511,33 +510,18 @@ public final class InputManager {
@TestApi
@NonNull
public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String layoutDescriptor) {
- KeyboardLayout[] layouts = getKeyboardLayouts();
- for (KeyboardLayout kl : layouts) {
- if (layoutDescriptor.equals(kl.getDescriptor())) {
- return kl.getLayoutType();
- }
- }
- return "";
+ KeyboardLayout layout = getKeyboardLayout(layoutDescriptor);
+ return layout == null ? "" : layout.getLayoutType();
}
/**
- * Gets information about all supported keyboard layouts appropriate
- * for a specific input device.
- * <p>
- * The input manager consults the built-in keyboard layouts as well
- * as all keyboard layouts advertised by applications using a
- * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
- * </p>
- *
- * @return A list of all supported keyboard layouts for a specific
- * input device.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
@NonNull
public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
@NonNull InputDeviceIdentifier identifier) {
- return mGlobal.getKeyboardLayoutsForInputDevice(identifier);
+ return new KeyboardLayout[0];
}
/**
@@ -549,6 +533,7 @@ public final class InputManager {
*
* @hide
*/
+ @Nullable
public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
if (keyboardLayoutDescriptor == null) {
throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
@@ -562,121 +547,45 @@ public final class InputManager {
}
/**
- * Gets the current keyboard layout descriptor for the specified input device.
- *
- * @param identifier Identifier for the input device
- * @return The keyboard layout descriptor, or null if no keyboard layout has been set.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
- @TestApi
@Nullable
public String getCurrentKeyboardLayoutForInputDevice(
@NonNull InputDeviceIdentifier identifier) {
- try {
- return mIm.getCurrentKeyboardLayoutForInputDevice(identifier);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return null;
}
/**
- * Sets the current keyboard layout descriptor for the specified input device.
- * <p>
- * This method may have the side-effect of causing the input device in question to be
- * reconfigured.
- * </p>
- *
- * @param identifier The identifier for the input device.
- * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
- @TestApi
- @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
public void setCurrentKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
- @NonNull String keyboardLayoutDescriptor) {
- mGlobal.setCurrentKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- }
+ @NonNull String keyboardLayoutDescriptor) {}
/**
- * Gets all keyboard layout descriptors that are enabled for the specified input device.
- *
- * @param identifier The identifier for the input device.
- * @return The keyboard layout descriptors.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
- if (identifier == null) {
- throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
- }
-
- try {
- return mIm.getEnabledKeyboardLayoutsForInputDevice(identifier);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return new String[0];
}
/**
- * Adds the keyboard layout descriptor for the specified input device.
- * <p>
- * This method may have the side-effect of causing the input device in question to be
- * reconfigured.
- * </p>
- *
- * @param identifier The identifier for the input device.
- * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to add.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
- @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
String keyboardLayoutDescriptor) {
- if (identifier == null) {
- throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
- }
- if (keyboardLayoutDescriptor == null) {
- throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
- }
-
- try {
- mIm.addKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
}
/**
- * Removes the keyboard layout descriptor for the specified input device.
- * <p>
- * This method may have the side-effect of causing the input device in question to be
- * reconfigured.
- * </p>
- *
- * @param identifier The identifier for the input device.
- * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to remove.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
- @TestApi
@RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
public void removeKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
@NonNull String keyboardLayoutDescriptor) {
- if (identifier == null) {
- throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
- }
- if (keyboardLayoutDescriptor == null) {
- throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
- }
-
- try {
- mIm.removeKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
}
/**
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 7c104a0ca946..7b29666d9a96 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1068,36 +1068,21 @@ public final class InputManagerGlobal {
}
/**
- * @see InputManager#getKeyboardLayoutsForInputDevice(InputDeviceIdentifier)
+ * TODO(b/330517633): Cleanup the unsupported API
*/
@NonNull
public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
@NonNull InputDeviceIdentifier identifier) {
- try {
- return mIm.getKeyboardLayoutsForInputDevice(identifier);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return new KeyboardLayout[0];
}
/**
- * @see InputManager#setCurrentKeyboardLayoutForInputDevice
- * (InputDeviceIdentifier, String)
+ * TODO(b/330517633): Cleanup the unsupported API
*/
- @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
public void setCurrentKeyboardLayoutForInputDevice(
@NonNull InputDeviceIdentifier identifier,
- @NonNull String keyboardLayoutDescriptor) {
- Objects.requireNonNull(identifier, "identifier must not be null");
- Objects.requireNonNull(keyboardLayoutDescriptor,
- "keyboardLayoutDescriptor must not be null");
- try {
- mIm.setCurrentKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
+ @NonNull String keyboardLayoutDescriptor) {}
+
/**
* @see InputDevice#getSensorManager()
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 4328d9fe4d06..4c5ebe786b24 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -16,6 +16,9 @@
package android.hardware.input;
+import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG;
+import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG;
+import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG;
import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
@@ -23,6 +26,7 @@ import static com.android.hardware.input.Flags.touchpadTapDragging;
import static com.android.input.flags.Flags.enableInputFilterRustImpl;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -465,6 +469,8 @@ public class InputSettings {
*
* @hide
*/
+ @TestApi
+ @FlaggedApi(FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG)
public static int getAccessibilityBounceKeysThreshold(@NonNull Context context) {
if (!isAccessibilityBounceKeysFeatureEnabled()) {
return 0;
@@ -487,6 +493,8 @@ public class InputSettings {
*
* @hide
*/
+ @TestApi
+ @FlaggedApi(FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG)
@RequiresPermission(Manifest.permission.WRITE_SETTINGS)
public static void setAccessibilityBounceKeysThreshold(@NonNull Context context,
int thresholdTimeMillis) {
@@ -545,6 +553,8 @@ public class InputSettings {
*
* @hide
*/
+ @TestApi
+ @FlaggedApi(FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG)
public static int getAccessibilitySlowKeysThreshold(@NonNull Context context) {
if (!isAccessibilitySlowKeysFeatureFlagEnabled()) {
return 0;
@@ -567,6 +577,8 @@ public class InputSettings {
*
* @hide
*/
+ @TestApi
+ @FlaggedApi(FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG)
@RequiresPermission(Manifest.permission.WRITE_SETTINGS)
public static void setAccessibilitySlowKeysThreshold(@NonNull Context context,
int thresholdTimeMillis) {
@@ -614,6 +626,8 @@ public class InputSettings {
*
* @hide
*/
+ @TestApi
+ @FlaggedApi(FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
public static boolean isAccessibilityStickyKeysEnabled(@NonNull Context context) {
if (!isAccessibilityStickyKeysFeatureEnabled()) {
return false;
@@ -635,6 +649,8 @@ public class InputSettings {
*
* @hide
*/
+ @TestApi
+ @FlaggedApi(FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
@RequiresPermission(Manifest.permission.WRITE_SETTINGS)
public static void setAccessibilityStickyKeysEnabled(@NonNull Context context,
boolean enabled) {
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index ed536cee7274..9684e6498bfa 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -1,5 +1,4 @@
package: "com.android.hardware.input"
-container: "system"
# Project link: https://gantry.corp.google.com/projects/android_platform_input_native/changes
diff --git a/core/java/android/hardware/radio/flags.aconfig b/core/java/android/hardware/radio/flags.aconfig
index c9ab62ddc27f..d0d10c17ee38 100644
--- a/core/java/android/hardware/radio/flags.aconfig
+++ b/core/java/android/hardware/radio/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.hardware.radio"
-container: "system"
flag {
name: "hd_radio_improved"
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 967fc42e8783..fac02ce652b2 100644
--- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -1,5 +1,4 @@
package: "android.hardware.usb.flags"
-container: "system"
flag {
name: "enable_usb_data_compliance_warning"
diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
index 94df16030cdb..3dd746c5fad3 100644
--- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -1,5 +1,4 @@
package: "android.hardware.usb.flags"
-container: "system"
flag {
name: "enable_is_pd_compliant_api"
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 048c50eb0a19..3544a691cce4 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.net.platform.flags"
-container: "system"
# This file contains aconfig flags used from platform code
# Flags used for module APIs must be in aconfig files under each modules
diff --git a/core/java/android/net/thread/flags.aconfig b/core/java/android/net/thread/flags.aconfig
index afb982ba64ca..ef798ad46d2d 100644
--- a/core/java/android/net/thread/flags.aconfig
+++ b/core/java/android/net/thread/flags.aconfig
@@ -1,5 +1,4 @@
package: "com.android.net.thread.platform.flags"
-container: "system"
# This file contains aconfig flags used from platform code
# Flags used for module APIs must be in aconfig files under each modules
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index 15d671dde02d..e64823af84cb 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.net.vcn"
-container: "system"
flag {
name: "safe_mode_config"
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 2fe115f49099..5b711c9d8401 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -25,8 +25,6 @@ import android.util.Printer;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
-import dalvik.annotation.optimization.CriticalNative;
-
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -80,7 +78,6 @@ public final class MessageQueue {
private native static void nativeDestroy(long ptr);
@UnsupportedAppUsage
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
- @CriticalNative
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
diff --git a/core/java/android/os/OomKillRecord.java b/core/java/android/os/OomKillRecord.java
index ca1d49a93def..78fbce63b00d 100644
--- a/core/java/android/os/OomKillRecord.java
+++ b/core/java/android/os/OomKillRecord.java
@@ -25,7 +25,7 @@ import com.android.internal.util.FrameworkStatsLog;
* Note that this class fields' should be equivalent to the struct
* <b>OomKill</b> inside
* <pre>
- * system/memory/libmeminfo/libmemevents/include/memevents.h
+ * system/memory/libmeminfo/libmemevents/include/memevents/bpf_types.h
* </pre>
*
* @hide
@@ -36,14 +36,27 @@ public final class OomKillRecord {
private int mUid;
private String mProcessName;
private short mOomScoreAdj;
+ private long mTotalVmInKb;
+ private long mAnonRssInKb;
+ private long mFileRssInKb;
+ private long mShmemRssInKb;
+ private long mPgTablesInKb;
public OomKillRecord(long timeStampInMillis, int pid, int uid,
- String processName, short oomScoreAdj) {
+ String processName, short oomScoreAdj,
+ long totalVmInKb, long anonRssInKb,
+ long fileRssInKb, long shmemRssInKb,
+ long pgTablesInKb) {
this.mTimeStampInMillis = timeStampInMillis;
this.mPid = pid;
this.mUid = uid;
this.mProcessName = processName;
this.mOomScoreAdj = oomScoreAdj;
+ this.mTotalVmInKb = totalVmInKb;
+ this.mAnonRssInKb = anonRssInKb;
+ this.mFileRssInKb = fileRssInKb;
+ this.mShmemRssInKb = shmemRssInKb;
+ this.mPgTablesInKb = pgTablesInKb;
}
/**
@@ -55,7 +68,8 @@ public final class OomKillRecord {
FrameworkStatsLog.write(
FrameworkStatsLog.KERNEL_OOM_KILL_OCCURRED,
mUid, mPid, mOomScoreAdj, mTimeStampInMillis,
- mProcessName);
+ mProcessName, mTotalVmInKb, mAnonRssInKb,
+ mFileRssInKb, mShmemRssInKb, mPgTablesInKb);
}
public long getTimestampMilli() {
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 8a1774291e9f..bb74a3e7f896 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -1436,10 +1436,10 @@ public class RecoverySystem {
* @throws IOException if the recovery system service could not be contacted
*/
private boolean requestLskf(String packageName, IntentSender sender) throws IOException {
- Log.i(TAG, String.format("<%s> is requesting LSFK", packageName));
+ Log.i(TAG, TextUtils.formatSimple("Package<%s> requesting LSKF", packageName));
try {
boolean validRequest = mService.requestLskf(packageName, sender);
- Log.i(TAG, String.format("LSKF Request isValid = %b", validRequest));
+ Log.i(TAG, TextUtils.formatSimple("LSKF Request isValid = %b", validRequest));
return validRequest;
} catch (RemoteException | SecurityException e) {
throw new IOException("could not request LSKF capture", e);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f172c3e52415..857a85d99fa6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -705,7 +705,7 @@ public class UserManager {
* {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_BLUETOOTH}
* can set this restriction using the DevicePolicyManager APIs mentioned below.
*
- * <p>Default is <code>true</code> for managed profiles and false otherwise.
+ * <p>Default is <code>true</code> for managed and private profiles, false otherwise.
*
* <p>When a device upgrades to {@link android.os.Build.VERSION_CODES#O}, the system sets it
* for all existing managed profiles.
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index fd955e21c38b..f26a797f0c12 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -1,6 +1,5 @@
package: "android.os"
container: "system"
-container: "system"
flag {
name: "android_os_build_vanilla_ice_cream"
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index eda755c05509..229d1195f6b7 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.os.vibrator"
-container: "system"
flag {
namespace: "haptics"
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 3441244d6c58..fe3fa8cf34f5 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -240,6 +240,16 @@ public final class PermissionManager {
public static final String EXTRA_PERMISSION_USAGES =
"android.permission.extra.PERMISSION_USAGES";
+ /**
+ * Specify what permissions are device aware. Only device aware permissions can be granted to
+ * a remote device.
+ * @hide
+ */
+ public static final Set<String> DEVICE_AWARE_PERMISSIONS =
+ Flags.deviceAwarePermissionsEnabled()
+ ? Set.of(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
+ : Collections.emptySet();
+
private final @NonNull Context mContext;
private final IPackageManager mPackageManager;
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index c26f351bc3a0..23ece310b926 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.permission.flags"
-container: "system"
flag {
name: "device_aware_permission_apis_enabled"
@@ -156,14 +155,3 @@ flag {
description: "Use runtime permission state to determine appop state"
bug: "266164193"
}
-
-flag {
- name: "ignore_apex_permissions"
- is_fixed_read_only: true
- namespace: "permissions"
- description: "Ignore APEX pacakges for permissions on V+"
- bug: "301320911"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b7d421ae51b5..dd93972820c0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8005,6 +8005,7 @@ public final class Settings {
*
* @hide
*/
+ @Readable
public static final String ACCESSIBILITY_BOUNCE_KEYS = "accessibility_bounce_keys";
/**
@@ -8016,6 +8017,7 @@ public final class Settings {
*
* @hide
*/
+ @Readable
public static final String ACCESSIBILITY_SLOW_KEYS = "accessibility_slow_keys";
/**
@@ -8025,6 +8027,7 @@ public final class Settings {
*
* @hide
*/
+ @Readable
public static final String ACCESSIBILITY_STICKY_KEYS = "accessibility_sticky_keys";
/**
@@ -10977,7 +10980,7 @@ public final class Settings {
"biometric_debug_enabled";
/**
- * Whether or not virtual sensors are enabled.
+ * Whether or not both fingerprint and face virtual sensors are enabled.
* @hide
*/
@TestApi
@@ -10985,6 +10988,22 @@ public final class Settings {
public static final String BIOMETRIC_VIRTUAL_ENABLED = "biometric_virtual_enabled";
/**
+ * Whether or not fingerprint virtual sensors are enabled.
+ * @hide
+ */
+ @FlaggedApi("com.android.server.biometrics.face_vhal_feature")
+ public static final String BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED =
+ "biometric_fingerprint_virtual_enabled";
+
+ /**
+ * Whether or not face virtual sensors are enabled.
+ * @hide
+ */
+ @FlaggedApi("com.android.server.biometrics.face_vhal_feature")
+ public static final String BIOMETRIC_FACE_VIRTUAL_ENABLED =
+ "biometric_face_virtual_enabled";
+
+ /**
* Whether or not biometric is allowed on Keyguard.
* @hide
*/
diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig
index 77353c282b86..d0cef83390b9 100644
--- a/core/java/android/provider/flags.aconfig
+++ b/core/java/android/provider/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.provider"
-container: "system"
flag {
name: "a11y_standalone_fab_enabled"
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 02e787b5256c..7f5b550c830a 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.security"
-container: "system"
flag {
name: "certificate_transparency_configuration"
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index c7d951b99cce..548f8aa8113a 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -1,5 +1,4 @@
package: "android.security"
-container: "system"
flag {
name: "extend_ecm_to_all_settings"
diff --git a/core/java/android/service/appprediction/flags/flags.aconfig b/core/java/android/service/appprediction/flags/flags.aconfig
index 953bc44de523..7f9764e82c5d 100644
--- a/core/java/android/service/appprediction/flags/flags.aconfig
+++ b/core/java/android/service/appprediction/flags/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.service.appprediction.flags"
-container: "system"
flag {
name: "service_features_api"
diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig
index d6425c397bbb..a3eff3becd49 100644
--- a/core/java/android/service/chooser/flags.aconfig
+++ b/core/java/android/service/chooser/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.service.chooser"
-container: "system"
flag {
name: "chooser_album_text"
diff --git a/core/java/android/service/controls/flags/flags.aconfig b/core/java/android/service/controls/flags/flags.aconfig
index 6f3a67d180b9..197f1bcbc001 100644
--- a/core/java/android/service/controls/flags/flags.aconfig
+++ b/core/java/android/service/controls/flags/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.service.controls.flags"
-container: "system"
flag {
name: "home_panel_dream"
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index c6d3d9b6da84..92f2c321c1db 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -65,7 +65,7 @@ import java.util.Set;
* @hide
*/
public final class CredentialProviderInfoFactory {
- private static final String TAG = "CredentialProviderInfoFactory";
+ private static final String TAG = CredentialManager.TAG;
private static final String TAG_CREDENTIAL_PROVIDER = "credential-provider";
private static final String TAG_CAPABILITIES = "capabilities";
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
index 2f45f34b8da2..cca4937f7499 100644
--- a/core/java/android/service/dreams/flags.aconfig
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.service.dreams"
-container: "system"
flag {
name: "dream_overlay_host"
@@ -11,7 +10,7 @@ flag {
flag {
name: "dream_handles_confirm_keys"
- namespace: "dreams"
+ namespace: "communal"
description: "This flag enables dreams processing confirm keys to show the bouncer or dismiss "
"the keyguard"
bug: "326975875"
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
index b0c55a9787c1..35cd3edcafcb 100644
--- a/core/java/android/service/notification/flags.aconfig
+++ b/core/java/android/service/notification/flags.aconfig
@@ -1,6 +1,5 @@
package: "android.service.notification"
container: "system"
-container: "system"
flag {
name: "ranking_update_ashmem"
diff --git a/core/java/android/service/voice/flags/flags.aconfig b/core/java/android/service/voice/flags/flags.aconfig
index 357cb47ec4b8..1ae7d8c34442 100644
--- a/core/java/android/service/voice/flags/flags.aconfig
+++ b/core/java/android/service/voice/flags/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.service.voice.flags"
-container: "system"
flag {
name: "allow_training_data_egress_from_hds"
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0fc51e74d570..582c90feb547 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -27,12 +27,15 @@ import static android.graphics.Matrix.MSKEW_Y;
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION;
import static com.android.window.flags.Flags.noConsecutiveVisibilityEvents;
+import static com.android.window.flags.Flags.offloadColorExtraction;
import android.animation.AnimationHandler;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -832,6 +835,15 @@ public abstract class WallpaperService extends Service {
}
/**
+ * Called when the dim amount of the wallpaper changed. This can be used to recompute the
+ * wallpaper colors based on the new dim, and call {@link #notifyColorsChanged()}.
+ * @hide
+ */
+ @FlaggedApi(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void onDimAmountChanged(float dimAmount) {
+ }
+
+ /**
* Called when an application has changed the desired virtual size of
* the wallpaper.
*/
@@ -1043,6 +1055,10 @@ public abstract class WallpaperService extends Service {
}
mPreviousWallpaperDimAmount = mWallpaperDimAmount;
+
+ // after the dim changes, allow colors to be immediately recomputed
+ mLastColorInvalidation = 0;
+ if (offloadColorExtraction()) onDimAmountChanged(mWallpaperDimAmount);
}
/**
diff --git a/core/java/android/speech/flags/speech_flags.aconfig b/core/java/android/speech/flags/speech_flags.aconfig
index 2a42357e45f8..fa3359264ab6 100644
--- a/core/java/android/speech/flags/speech_flags.aconfig
+++ b/core/java/android/speech/flags/speech_flags.aconfig
@@ -1,5 +1,4 @@
package: "android.speech.flags"
-container: "system"
flag {
name: "multilang_extra_launch"
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index a8a0c5b8bdf7..24035af7c0b1 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -1,5 +1,4 @@
package: "com.android.text.flags"
-container: "system"
flag {
name: "vendor_custom_locale_fallback"
diff --git a/core/java/android/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig
index c50c384c6e4d..1815f14726d4 100644
--- a/core/java/android/tracing/flags.aconfig
+++ b/core/java/android/tracing/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.tracing"
-container: "system"
flag {
name: "perfetto_transition_tracing"
diff --git a/core/java/android/tracing/perfetto/DataSource.java b/core/java/android/tracing/perfetto/DataSource.java
index d0c719b86ac9..c33fa7dffad6 100644
--- a/core/java/android/tracing/perfetto/DataSource.java
+++ b/core/java/android/tracing/perfetto/DataSource.java
@@ -20,6 +20,8 @@ import android.util.proto.ProtoInputStream;
import com.android.internal.annotations.VisibleForTesting;
+import dalvik.annotation.optimization.CriticalNative;
+
/**
* Templated base class meant to be derived by embedders to create a custom data
* source.
@@ -71,9 +73,24 @@ public abstract class DataSource<DataSourceInstanceType extends DataSourceInstan
* @param fun The tracing lambda that will be called with the tracing contexts of each active
* tracing instance.
*/
- public final void trace(
- TraceFunction<DataSourceInstanceType, TlsStateType, IncrementalStateType> fun) {
- nativeTrace(mNativeObj, fun);
+ public final void trace(TraceFunction<TlsStateType, IncrementalStateType> fun) {
+ boolean startedIterator = nativePerfettoDsTraceIterateBegin(mNativeObj);
+
+ if (!startedIterator) {
+ return;
+ }
+
+ try {
+ do {
+ TracingContext<TlsStateType, IncrementalStateType> ctx =
+ new TracingContext<>(mNativeObj);
+ fun.trace(ctx);
+
+ ctx.flush();
+ } while (nativePerfettoDsTraceIterateNext(mNativeObj));
+ } finally {
+ nativePerfettoDsTraceIterateBreak(mNativeObj);
+ }
}
/**
@@ -154,8 +171,6 @@ public abstract class DataSource<DataSourceInstanceType extends DataSourceInstan
long dataSourcePtr, int bufferExhaustedPolicy);
private static native long nativeCreate(DataSource thiz, String name);
- private static native void nativeTrace(
- long nativeDataSourcePointer, TraceFunction traceFunction);
private static native void nativeFlushAll(long nativeDataSourcePointer);
private static native long nativeGetFinalizer();
@@ -163,4 +178,11 @@ public abstract class DataSource<DataSourceInstanceType extends DataSourceInstan
long dataSourcePtr, int dsInstanceIdx);
private static native void nativeReleasePerfettoInstanceLocked(
long dataSourcePtr, int dsInstanceIdx);
+
+ @CriticalNative
+ private static native boolean nativePerfettoDsTraceIterateBegin(long dataSourcePtr);
+ @CriticalNative
+ private static native boolean nativePerfettoDsTraceIterateNext(long dataSourcePtr);
+ @CriticalNative
+ private static native void nativePerfettoDsTraceIterateBreak(long dataSourcePtr);
}
diff --git a/core/java/android/tracing/perfetto/TraceFunction.java b/core/java/android/tracing/perfetto/TraceFunction.java
index 62941df70a48..13e663d607e9 100644
--- a/core/java/android/tracing/perfetto/TraceFunction.java
+++ b/core/java/android/tracing/perfetto/TraceFunction.java
@@ -16,19 +16,15 @@
package android.tracing.perfetto;
-import java.io.IOException;
-
/**
* The interface for the trace function called from native on a trace call with a context.
*
- * @param <DataSourceInstanceType> The type of DataSource this tracing context is for.
* @param <TlsStateType> The type of the custom TLS state, if any is used.
* @param <IncrementalStateType> The type of the custom incremental state, if any is used.
*
* @hide
*/
-public interface TraceFunction<DataSourceInstanceType extends DataSourceInstance,
- TlsStateType, IncrementalStateType> {
+public interface TraceFunction<TlsStateType, IncrementalStateType> {
/**
* This function will be called synchronously (i.e., always before trace() returns) only if
@@ -38,6 +34,5 @@ public interface TraceFunction<DataSourceInstanceType extends DataSourceInstance
*
* @param ctx the tracing context to trace for in the trace function.
*/
- void trace(TracingContext<DataSourceInstanceType, TlsStateType, IncrementalStateType> ctx)
- throws IOException;
+ void trace(TracingContext<TlsStateType, IncrementalStateType> ctx);
}
diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java
index c1a61a7c2c34..060f9649bb06 100644
--- a/core/java/android/tracing/perfetto/TracingContext.java
+++ b/core/java/android/tracing/perfetto/TracingContext.java
@@ -24,26 +24,18 @@ import java.util.List;
/**
* Argument passed to the lambda function passed to Trace().
*
- * @param <DataSourceInstanceType> The type of the datasource this tracing context is for.
* @param <TlsStateType> The type of the custom TLS state, if any is used.
* @param <IncrementalStateType> The type of the custom incremental state, if any is used.
*
* @hide
*/
-public class TracingContext<DataSourceInstanceType extends DataSourceInstance,
- TlsStateType, IncrementalStateType> {
+public class TracingContext<TlsStateType, IncrementalStateType> {
- private final long mContextPtr;
- private final TlsStateType mTlsState;
- private final IncrementalStateType mIncrementalState;
+ private final long mNativeDsPtr;
private final List<ProtoOutputStream> mTracePackets = new ArrayList<>();
- // Should only be created from the native side.
- private TracingContext(long contextPtr, TlsStateType tlsState,
- IncrementalStateType incrementalState) {
- this.mContextPtr = contextPtr;
- this.mTlsState = tlsState;
- this.mIncrementalState = incrementalState;
+ TracingContext(long nativeDsPtr) {
+ this.mNativeDsPtr = nativeDsPtr;
}
/**
@@ -69,7 +61,7 @@ public class TracingContext<DataSourceInstanceType extends DataSourceInstance,
* Stop timeout expires.
*/
public void flush() {
- nativeFlush(this, mContextPtr);
+ nativeFlush(mNativeDsPtr, getAndClearAllPendingTracePackets());
}
/**
@@ -80,7 +72,7 @@ public class TracingContext<DataSourceInstanceType extends DataSourceInstance,
* @return The TlsState instance for the tracing thread and instance.
*/
public TlsStateType getCustomTlsState() {
- return this.mTlsState;
+ return (TlsStateType) nativeGetCustomTls(mNativeDsPtr);
}
/**
@@ -90,10 +82,9 @@ public class TracingContext<DataSourceInstanceType extends DataSourceInstance,
* @return The current IncrementalState object instance.
*/
public IncrementalStateType getIncrementalState() {
- return this.mIncrementalState;
+ return (IncrementalStateType) nativeGetIncrementalState(mNativeDsPtr);
}
- // Called from native to get trace packets
private byte[][] getAndClearAllPendingTracePackets() {
byte[][] res = new byte[mTracePackets.size()][];
for (int i = 0; i < mTracePackets.size(); i++) {
@@ -105,5 +96,7 @@ public class TracingContext<DataSourceInstanceType extends DataSourceInstance,
return res;
}
- private static native void nativeFlush(TracingContext thiz, long ctxPointer);
+ private static native void nativeFlush(long dataSourcePtr, byte[][] packetData);
+ private static native Object nativeGetCustomTls(long nativeDsPtr);
+ private static native Object nativeGetIncrementalState(long nativeDsPtr);
}
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index ea7efc79de87..c1ed19fef032 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -203,9 +203,8 @@ public final class PackageUtils {
}
File f = new File(filePath);
- try {
- DigestInputStream digestInputStream = new DigestInputStream(new FileInputStream(f),
- messageDigest);
+ try (DigestInputStream digestInputStream = new DigestInputStream(new FileInputStream(f),
+ messageDigest)) {
while (digestInputStream.read(fileBuffer) != -1);
} catch (IOException e) {
e.printStackTrace();
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index f4dadbb0d25a..beb4d95263be 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -17,6 +17,7 @@
package android.view;
import static com.android.text.flags.Flags.handwritingCursorPosition;
+import static com.android.text.flags.Flags.handwritingUnsupportedMessage;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
@@ -607,7 +608,9 @@ public class HandwritingInitiator {
final View candidateView = findBestCandidateView(hoverX, hoverY, /* isHover */ true);
if (candidateView != null) {
- mCachedHoverTarget = new WeakReference<>(candidateView);
+ if (!handwritingUnsupportedMessage()) {
+ mCachedHoverTarget = new WeakReference<>(candidateView);
+ }
return candidateView;
}
}
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 253073a83827..69228cafa34b 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -83,11 +83,7 @@ public class HapticFeedbackConstants {
*/
public static final int TEXT_HANDLE_MOVE = 9;
- /**
- * The user unlocked the device
- * @hide
- */
- public static final int ENTRY_BUMP = 10;
+ // REMOVED: ENTRY_BUMP = 10
/**
* The user has moved the dragged object within a droppable area.
@@ -230,6 +226,22 @@ public class HapticFeedbackConstants {
public static final int LONG_PRESS_POWER_BUTTON = 10003;
/**
+ * A haptic effect to signal the confirmation of a user biometric authentication
+ * (e.g. fingerprint reading).
+ * This is a private constant to be used only by system apps.
+ * @hide
+ */
+ public static final int BIOMETRIC_CONFIRM = 10004;
+
+ /**
+ * A haptic effect to signal the rejection of a user biometric authentication attempt
+ * (e.g. fingerprint reading).
+ * This is a private constant to be used only by system apps.
+ * @hide
+ */
+ public static final int BIOMETRIC_REJECT = 10005;
+
+ /**
* Flag for {@link View#performHapticFeedback(int, int)
* View.performHapticFeedback(int, int)}: Ignore the setting in the
* view for whether to perform haptic feedback, do it always.
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index b30002228d54..6caf4d6ff992 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -196,11 +196,11 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
if (!super.setControl(control, showTypes, hideTypes)) {
return false;
}
- final boolean hasLeash = control != null && control.getLeash() != null;
- if (!hasLeash && !mIsRequestedVisibleAwaitingLeash) {
+ if (control == null && !mIsRequestedVisibleAwaitingLeash) {
mController.setRequestedVisibleTypes(0 /* visibleTypes */, getType());
removeSurface();
}
+ final boolean hasLeash = control != null && control.getLeash() != null;
if (hasLeash) {
mIsRequestedVisibleAwaitingLeash = false;
}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 0ae3e598f8b9..56a24e4705b7 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -3783,6 +3783,13 @@ public final class MotionEvent extends InputEvent implements Parcelable {
throw new IllegalArgumentException(
"idBits must contain at least one pointer from this motion event");
}
+ final int currentBits = getPointerIdBits();
+ if ((currentBits & idBits) != idBits) {
+ throw new IllegalArgumentException(
+ "idBits must be a non-empty subset of the pointer IDs from this MotionEvent, "
+ + "got idBits: "
+ + String.format("0x%x", idBits) + " for " + this);
+ }
MotionEvent event = obtain();
event.mNativePtr = nativeSplit(event.mNativePtr, this.mNativePtr, idBits);
return event;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index da6cd40a0c8c..c5a4d677e70e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -9587,6 +9587,8 @@ public final class ViewRootImpl implements ViewParent,
}
mRemoved = true;
mOnBackInvokedDispatcher.detachFromWindow();
+ removeVrrMessages();
+
if (mAdded) {
dispatchDetachedFromWindow();
}
@@ -12551,8 +12553,8 @@ public final class ViewRootImpl implements ViewParent,
if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
frameRateCategory, false).applyAsyncUnsafe();
- mLastPreferredFrameRateCategory = frameRateCategory;
}
+ mLastPreferredFrameRateCategory = frameRateCategory;
}
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate category", e);
@@ -12612,8 +12614,8 @@ public final class ViewRootImpl implements ViewParent,
if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
mFrameRateCompatibility).applyAsyncUnsafe();
- mLastPreferredFrameRate = preferredFrameRate;
}
+ mLastPreferredFrameRate = preferredFrameRate;
}
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate", e);
@@ -12855,4 +12857,10 @@ public final class ViewRootImpl implements ViewParent,
mHasIdledMessage = true;
}
}
+
+ private void removeVrrMessages() {
+ mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT);
+ mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE);
+ mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
+ }
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 59cb45019e83..afe5b7e15793 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1565,8 +1565,9 @@ public interface WindowManager extends ViewManager {
* android:value="true|false"/&gt;
* &lt;/activity&gt;
* </pre>
+ *
+ * @hide
*/
- @FlaggedApi(Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING)
String PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING =
"android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING";
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index f4aef22e7fee..334965abd8c9 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -1,5 +1,4 @@
package: "android.view.accessibility"
-container: "system"
# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
@@ -81,6 +80,16 @@ flag {
}
flag {
+ name: "migrate_enable_shortcuts"
+ namespace: "accessibility"
+ description: "Refactors deprecated code to use AccessibilityManager#enableShortcutsForTargets."
+ bug: "332006721"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "motion_event_observing"
is_exported: true
namespace: "accessibility"
diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
index 416a877d87ab..3c15518419b5 100644
--- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
+++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
@@ -1,5 +1,4 @@
package: "android.view.contentcapture.flags"
-container: "system"
flag {
name: "run_on_background_thread_enabled"
diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
index b3bd92b37357..4de0f29c60fe 100644
--- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
+++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
@@ -1,5 +1,4 @@
package: "android.view.contentprotection.flags"
-container: "system"
flag {
name: "blocklist_update_enabled"
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index d0fe3e0ecb3a..1047131c5416 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -1,5 +1,4 @@
package: "android.view.flags"
-container: "system"
flag {
name: "view_velocity_api"
diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig
index 338037f705e4..a7c41046b5b4 100644
--- a/core/java/android/view/flags/scroll_feedback_flags.aconfig
+++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig
@@ -1,5 +1,4 @@
package: "android.view.flags"
-container: "system"
flag {
namespace: "toolkit"
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index e8e02ecc00d2..c482f8be7315 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -1,5 +1,4 @@
package: "android.view.flags"
-container: "system"
flag {
name: "enable_surface_native_alloc_registration_ro"
diff --git a/core/java/android/view/flags/window_insets.aconfig b/core/java/android/view/flags/window_insets.aconfig
index bedb7d578517..bf6df5ca21cf 100644
--- a/core/java/android/view/flags/window_insets.aconfig
+++ b/core/java/android/view/flags/window_insets.aconfig
@@ -1,5 +1,4 @@
package: "android.view.flags"
-container: "system"
flag {
name: "customizable_window_headers"
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index d79903bfd06b..0d197468e3e3 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.view.inputmethod"
-container: "system"
flag {
name: "refactor_insets_controller"
diff --git a/core/java/android/webkit/flags.aconfig b/core/java/android/webkit/flags.aconfig
index defe61e506af..2d834a8b2384 100644
--- a/core/java/android/webkit/flags.aconfig
+++ b/core/java/android/webkit/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.webkit"
-container: "system"
flag {
name: "update_service_ipc_wrapper"
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 65984f55ded2..ead8887fda35 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -45,8 +45,10 @@ import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.IAccessibilityManager;
+import android.widget.flags.Flags;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -205,27 +207,41 @@ public class Toast {
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
- tn.mNextView = new WeakReference<>(mNextView);
+ if (Flags.toastNoWeakref()) {
+ tn.mNextView = mNextView;
+ } else {
+ tn.mNextViewWeakRef = new WeakReference<>(mNextView);
+ }
final boolean isUiContext = mContext.isUiContext();
final int displayId = mContext.getDisplayId();
+ boolean wasEnqueued = false;
try {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
if (mNextView != null) {
// It's a custom toast
- service.enqueueToast(pkg, mToken, tn, mDuration, isUiContext, displayId);
+ wasEnqueued = service.enqueueToast(pkg, mToken, tn, mDuration, isUiContext,
+ displayId);
} else {
// It's a text toast
ITransientNotificationCallback callback =
new CallbackBinder(mCallbacks, mHandler);
- service.enqueueTextToast(pkg, mToken, mText, mDuration, isUiContext, displayId,
- callback);
+ wasEnqueued = service.enqueueTextToast(pkg, mToken, mText, mDuration,
+ isUiContext, displayId, callback);
}
} else {
- service.enqueueToast(pkg, mToken, tn, mDuration, isUiContext, displayId);
+ wasEnqueued = service.enqueueToast(pkg, mToken, tn, mDuration, isUiContext,
+ displayId);
}
} catch (RemoteException e) {
// Empty
+ } finally {
+ if (Flags.toastNoWeakref()) {
+ if (!wasEnqueued) {
+ tn.mNextViewWeakRef = null;
+ tn.mNextView = null;
+ }
+ }
}
}
@@ -581,6 +597,16 @@ public class Toast {
}
}
+ /**
+ * Get the Toast.TN ITransientNotification object
+ * @return TN
+ * @hide
+ */
+ @VisibleForTesting
+ public TN getTn() {
+ return mTN;
+ }
+
// =======================================================================================
// All the gunk below is the interaction with the Notification Service, which handles
// the proper ordering of these system-wide.
@@ -599,7 +625,11 @@ public class Toast {
return sService;
}
- private static class TN extends ITransientNotification.Stub {
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public static class TN extends ITransientNotification.Stub {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private final WindowManager.LayoutParams mParams;
@@ -620,7 +650,9 @@ public class Toast {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
View mView;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
- WeakReference<View> mNextView;
+ WeakReference<View> mNextViewWeakRef;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ View mNextView;
int mDuration;
WindowManager mWM;
@@ -662,14 +694,22 @@ public class Toast {
handleHide();
// Don't do this in handleHide() because it is also invoked by
// handleShow()
- mNextView = null;
+ if (Flags.toastNoWeakref()) {
+ mNextView = null;
+ } else {
+ mNextViewWeakRef = null;
+ }
break;
}
case CANCEL: {
handleHide();
// Don't do this in handleHide() because it is also invoked by
// handleShow()
- mNextView = null;
+ if (Flags.toastNoWeakref()) {
+ mNextView = null;
+ } else {
+ mNextViewWeakRef = null;
+ }
try {
getService().cancelToast(mPackageName, mToken);
} catch (RemoteException e) {
@@ -716,21 +756,43 @@ public class Toast {
}
public void handleShow(IBinder windowToken) {
- if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
- + " mNextView=" + mNextView);
+ if (Flags.toastNoWeakref()) {
+ if (localLOGV) {
+ Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
+ + " mNextView=" + mNextView);
+ }
+ } else {
+ if (localLOGV) {
+ Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
+ + " mNextView=" + mNextViewWeakRef);
+ }
+ }
// If a cancel/hide is pending - no need to show - at this point
// the window token is already invalid and no need to do any work.
if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
return;
}
- if (mNextView != null && mView != mNextView.get()) {
- // remove the old view if necessary
- handleHide();
- mView = mNextView.get();
- if (mView != null) {
- mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
- mHorizontalMargin, mVerticalMargin,
- new CallbackBinder(getCallbacks(), mHandler));
+ if (Flags.toastNoWeakref()) {
+ if (mNextView != null && mView != mNextView) {
+ // remove the old view if necessary
+ handleHide();
+ mView = mNextView;
+ if (mView != null) {
+ mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
+ mHorizontalMargin, mVerticalMargin,
+ new CallbackBinder(getCallbacks(), mHandler));
+ }
+ }
+ } else {
+ if (mNextViewWeakRef != null && mView != mNextViewWeakRef.get()) {
+ // remove the old view if necessary
+ handleHide();
+ mView = mNextViewWeakRef.get();
+ if (mView != null) {
+ mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
+ mHorizontalMargin, mVerticalMargin,
+ new CallbackBinder(getCallbacks(), mHandler));
+ }
}
}
}
@@ -745,6 +807,23 @@ public class Toast {
mView = null;
}
}
+
+ /**
+ * Get the next view to show for enqueued toasts
+ * Custom toast views are deprecated.
+ * @see #setView(View)
+ *
+ * @return next view
+ * @hide
+ */
+ @VisibleForTesting
+ public View getNextView() {
+ if (Flags.toastNoWeakref()) {
+ return mNextView;
+ } else {
+ return (mNextViewWeakRef != null) ? mNextViewWeakRef.get() : null;
+ }
+ }
}
/**
diff --git a/core/java/android/widget/flags/differential_motion_fling_flags.aconfig b/core/java/android/widget/flags/differential_motion_fling_flags.aconfig
index a0a391e58dfe..79cfe566ac05 100644
--- a/core/java/android/widget/flags/differential_motion_fling_flags.aconfig
+++ b/core/java/android/widget/flags/differential_motion_fling_flags.aconfig
@@ -1,5 +1,4 @@
package: "android.widget.flags"
-container: "system"
flag {
namespace: "toolkit"
diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig
index b530e7197ab5..515fa55b7b90 100644
--- a/core/java/android/widget/flags/notification_widget_flags.aconfig
+++ b/core/java/android/widget/flags/notification_widget_flags.aconfig
@@ -1,5 +1,4 @@
package: "android.widget.flags"
-container: "system"
flag {
name: "notif_linearlayout_optimized"
@@ -26,4 +25,14 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
+
+flag {
+ name: "toast_no_weakref"
+ namespace: "systemui"
+ description: "Do not use WeakReference for custom view Toast"
+ bug: "321732224"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 5227724e705e..994e73288e93 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -538,6 +538,11 @@ public final class TransitionInfo implements Parcelable {
// If the change has no parent (it is root), then it is independent
if (change.getParent() == null) return true;
+ if (change.getLastParent() != null && !change.getLastParent().equals(change.getParent())) {
+ // If the change has been reparented, then it's independent.
+ return true;
+ }
+
// non-visibility changes will just be folded into the parent change, so they aren't
// independent either.
if (change.getMode() == TRANSIT_CHANGE) return false;
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 4a3aba13fd54..a868d487b82f 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -25,6 +25,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.app.ResourcesManager;
+import android.app.servertransaction.ClientTransactionListenerController;
import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
@@ -137,12 +138,24 @@ public class WindowTokenClient extends Binder {
* should be dispatched to listeners.
*/
@AnyThread
- public void onConfigurationChanged(Configuration newConfig, int newDisplayId,
+ public void onConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId,
boolean shouldReportConfigChange) {
final Context context = mContextRef.get();
if (context == null) {
return;
}
+ final ClientTransactionListenerController controller =
+ ClientTransactionListenerController.getInstance();
+ controller.onContextConfigurationPreChanged(context);
+ try {
+ onConfigurationChangedInner(context, newConfig, newDisplayId, shouldReportConfigChange);
+ } finally {
+ controller.onContextConfigurationPostChanged(context);
+ }
+ }
+
+ private void onConfigurationChangedInner(@NonNull Context context,
+ @NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) {
CompatibilityInfo.applyOverrideScaleIfNeeded(newConfig);
final boolean displayChanged;
final boolean shouldUpdateResources;
diff --git a/core/java/android/window/flags/accessibility.aconfig b/core/java/android/window/flags/accessibility.aconfig
index 733e3db42fea..368c6090b6fa 100644
--- a/core/java/android/window/flags/accessibility.aconfig
+++ b/core/java/android/window/flags/accessibility.aconfig
@@ -1,5 +1,4 @@
package: "com.android.window.flags"
-container: "system"
flag {
name: "do_not_check_intersection_when_non_magnifiable_window_transitions"
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 98ff3c6bc347..fa0dab09a8b3 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -1,5 +1,4 @@
package: "com.android.window.flags"
-container: "system"
flag {
name: "allows_screen_size_decoupled_from_status_bar_and_cutout"
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index efe31fff411e..9524a6ec8f7b 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -1,5 +1,4 @@
package: "com.android.window.flags"
-container: "system"
flag {
name: "enable_scaled_resizing"
@@ -36,3 +35,17 @@ flag {
description: "Enables a limit on the number of Tasks shown in Desktop Mode"
bug: "332502912"
}
+
+flag {
+ name: "enable_windowing_edge_drag_resize"
+ namespace: "lse_desktop_experience"
+ description: "Enables edge drag resizing for all input sources"
+ bug: "323383067"
+}
+
+flag {
+ name: "enable_desktop_windowing_taskbar_running_apps"
+ namespace: "lse_desktop_experience"
+ description: "Shows running apps in Desktop Mode Taskbar"
+ bug: "332504528"
+}
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index 33af48636f02..94c72c60ecd0 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -1,5 +1,4 @@
package: "com.android.window.flags"
-container: "system"
flag {
name: "bal_require_opt_in_by_pending_intent_creator"
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index aa92af228862..edf90b5c141e 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -1,5 +1,4 @@
package: "com.android.window.flags"
-container: "system"
flag {
name: "always_update_wallpaper_permission"
@@ -21,4 +20,14 @@ flag {
namespace: "systemui"
description: "Prevent the system from sending consecutive onVisibilityChanged(false) events."
bug: "285631818"
+}
+
+flag {
+ name: "offload_color_extraction"
+ namespace: "systemui"
+ description: "Let ImageWallpaper take care of its wallpaper color extraction, instead of system_server"
+ bug: "328791519"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 460df3103488..5c310484eff9 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -1,5 +1,4 @@
package: "com.android.window.flags"
-container: "system"
# Project link: https://gantry.corp.google.com/projects/android_platform_window_surfaces/changes
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index dd6b772ab871..e2efff39f74d 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -1,5 +1,4 @@
package: "com.android.window.flags"
-container: "system"
flag {
name: "nav_bar_transparent_by_default"
@@ -128,9 +127,28 @@ flag {
}
flag {
+ name: "fifo_priority_for_major_ui_processes"
+ namespace: "windowing_frontend"
+ description: "Use realtime priority for SystemUI and launcher"
+ bug: "288140556"
+ is_fixed_read_only: true
+}
+
+flag {
name: "insets_decoupled_configuration"
namespace: "windowing_frontend"
description: "Configuration decoupled from insets"
bug: "151861875"
is_fixed_read_only: true
+}
+
+flag {
+ name: "keyguard_appear_transition"
+ namespace: "windowing_frontend"
+ description: "Add transition when keyguard appears"
+ bug: "327970608"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 80265ecaffe3..4b3d8e809eca 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -1,5 +1,4 @@
package: "com.android.window.flags"
-container: "system"
# Project link: https://gantry.corp.google.com/projects/android_platform_windowing_sdk/changes
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index a0c405e31e79..e531bcbaa215 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -24,8 +24,10 @@ import static com.android.internal.accessibility.dialog.AccessibilityTargetHelpe
import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
import static com.android.internal.util.ArrayUtils.convertToLongArray;
+import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AlertDialog;
@@ -53,6 +55,7 @@ import android.util.Slog;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import android.widget.Toast;
import com.android.internal.R;
@@ -328,11 +331,14 @@ public class AccessibilityShortcutController {
warningToast.show();
}
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
private AlertDialog createShortcutWarningDialog(int userId) {
List<AccessibilityTarget> targets = getTargets(mContext, HARDWARE);
if (targets.size() == 0) {
return null;
}
+ final AccessibilityManager am = mFrameworkObjectProvider
+ .getAccessibilityManagerInstance(mContext);
// Avoid non-a11y users accidentally turning shortcut on without reading this carefully.
// Put "don't turn on" as the primary action.
@@ -360,10 +366,15 @@ public class AccessibilityShortcutController {
// to the Settings.
final ComponentName configDefaultService =
ComponentName.unflattenFromString(defaultService);
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
- configDefaultService.flattenToString(),
- userId);
+ if (Flags.migrateEnableShortcuts()) {
+ am.enableShortcutsForTargets(true, HARDWARE,
+ Set.of(configDefaultService.flattenToString()), userId);
+ } else {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ configDefaultService.flattenToString(),
+ userId);
+ }
}
})
.setPositiveButton(R.string.accessibility_shortcut_off,
@@ -373,13 +384,16 @@ public class AccessibilityShortcutController {
mContext,
HARDWARE,
userId);
-
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
- userId);
- ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
- mContext, targetServices, userId);
-
+ if (Flags.migrateEnableShortcuts()) {
+ am.enableShortcutsForTargets(
+ false, HARDWARE, targetServices, userId);
+ } else {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
+ userId);
+ ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
+ mContext, targetServices, userId);
+ }
// If canceled, treat as if the dialog has never been shown
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index ba1dffcec73f..66faa318666d 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -23,11 +23,14 @@ import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueF
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -35,6 +38,8 @@ import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutT
import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.Set;
+
/**
* Abstract base class for creating various target related to accessibility service, accessibility
* activity, and allowlisting features.
@@ -108,13 +113,21 @@ public abstract class AccessibilityTarget implements TargetOperations, OnTargetS
}
}
+ @SuppressLint("MissingPermission")
@Override
public void onCheckedChanged(boolean isChecked) {
setShortcutEnabled(isChecked);
- if (isChecked) {
- optInValueToSettings(getContext(), getShortcutType(), getId());
+ if (Flags.migrateEnableShortcuts()) {
+ final AccessibilityManager am =
+ getContext().getSystemService(AccessibilityManager.class);
+ am.enableShortcutsForTargets(
+ isChecked, getShortcutType(), Set.of(mId), UserHandle.myUserId());
} else {
- optOutValueFromSettings(getContext(), getShortcutType(), getId());
+ if (isChecked) {
+ optInValueToSettings(getContext(), getShortcutType(), getId());
+ } else {
+ optOutValueFromSettings(getContext(), getShortcutType(), getId());
+ }
}
}
diff --git a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
index 753597914782..f9efb5059c09 100644
--- a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
@@ -17,17 +17,11 @@
package com.android.internal.accessibility.dialog;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
-import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
-import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
-import android.content.ComponentName;
import android.content.Context;
-import android.widget.Toast;
-import com.android.internal.R;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
@@ -47,30 +41,10 @@ class VolumeShortcutToggleAccessibilityServiceTarget extends AccessibilityServic
@Override
public void onCheckedChanged(boolean isChecked) {
- switch (getShortcutType()) {
- case SOFTWARE:
- onCheckedFromAccessibilityButton(isChecked);
- return;
- case HARDWARE:
- super.onCheckedChanged(isChecked);
- return;
- default:
- throw new IllegalStateException("Unexpected shortcut type");
- }
- }
-
- private void onCheckedFromAccessibilityButton(boolean isChecked) {
- setShortcutEnabled(isChecked);
- final ComponentName componentName = ComponentName.unflattenFromString(getId());
- setAccessibilityServiceState(getContext(), componentName, isChecked);
-
- if (!isChecked) {
- optOutValueFromSettings(getContext(), UserShortcutType.HARDWARE, getId());
-
- final String warningText =
- getContext().getString(R.string.accessibility_uncheck_legacy_item_warning,
- getLabel());
- Toast.makeText(getContext(), warningText, Toast.LENGTH_SHORT).show();
+ if (getShortcutType() == HARDWARE) {
+ super.onCheckedChanged(isChecked);
+ } else {
+ throw new IllegalStateException("Unexpected shortcut type");
}
}
}
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index eef6ce7619e8..07fa679a428a 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -229,6 +229,17 @@ public final class LongArrayMultiStateCounter implements Parcelable {
}
/**
+ * Copies time-in-state and timestamps from the supplied counter.
+ */
+ public void copyStatesFrom(LongArrayMultiStateCounter counter) {
+ if (mStateCount != counter.mStateCount) {
+ throw new IllegalArgumentException(
+ "State count is not the same: " + mStateCount + " vs. " + counter.mStateCount);
+ }
+ native_copyStatesFrom(mNativeObject, counter.mNativeObject);
+ }
+
+ /**
* Sets the new values for the given state.
*/
public void setValues(int state, long[] values) {
@@ -376,6 +387,10 @@ public final class LongArrayMultiStateCounter implements Parcelable {
private static native void native_setState(long nativeObject, int state, long timestampMs);
@CriticalNative
+ private static native void native_copyStatesFrom(long nativeObjectTarget,
+ long nativeObjectSource);
+
+ @CriticalNative
private static native void native_setValues(long nativeObject, int state,
long longArrayContainerNativeObject);
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index ab982f5b67cf..9646ae957720 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -18,6 +18,7 @@ package com.android.internal.os;
import android.annotation.LongDef;
+import android.annotation.Nullable;
import android.annotation.StringDef;
import android.annotation.XmlRes;
import android.compat.annotation.UnsupportedAppUsage;
@@ -352,19 +353,39 @@ public class PowerProfile {
* WARNING: use only for testing!
*/
@VisibleForTesting
- public void forceInitForTesting(Context context, @XmlRes int xmlId) {
+ public void initForTesting(XmlPullParser parser) {
+ initForTesting(parser, null);
+ }
+
+ /**
+ * Reinitialize the PowerProfile with the provided XML, using optional Resources for fallback
+ * configuration settings.
+ * WARNING: use only for testing!
+ */
+ @VisibleForTesting
+ public void initForTesting(XmlPullParser parser, @Nullable Resources resources) {
synchronized (sLock) {
sPowerItemMap.clear();
sPowerArrayMap.clear();
sModemPowerProfile.clear();
- initLocked(context, xmlId);
+
+ try {
+ readPowerValuesFromXml(parser, resources);
+ } finally {
+ if (parser instanceof XmlResourceParser) {
+ ((XmlResourceParser) parser).close();
+ }
+ }
+ initLocked();
}
}
@GuardedBy("sLock")
private void initLocked(Context context, @XmlRes int xmlId) {
if (sPowerItemMap.size() == 0 && sPowerArrayMap.size() == 0) {
- readPowerValuesFromXml(context, xmlId);
+ final Resources resources = context.getResources();
+ XmlResourceParser parser = resources.getXml(xmlId);
+ readPowerValuesFromXml(parser, resources);
}
initLocked();
}
@@ -377,9 +398,8 @@ public class PowerProfile {
initModem();
}
- private void readPowerValuesFromXml(Context context, @XmlRes int xmlId) {
- final Resources resources = context.getResources();
- XmlResourceParser parser = resources.getXml(xmlId);
+ private static void readPowerValuesFromXml(XmlPullParser parser,
+ @Nullable Resources resources) {
boolean parsingArray = false;
ArrayList<Double> array = new ArrayList<>();
String arrayName = null;
@@ -430,9 +450,17 @@ public class PowerProfile {
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
- parser.close();
+ if (parser instanceof XmlResourceParser) {
+ ((XmlResourceParser) parser).close();
+ }
}
+ if (resources != null) {
+ getDefaultValuesFromConfig(resources);
+ }
+ }
+
+ private static void getDefaultValuesFromConfig(Resources resources) {
// Now collect other config variables.
int[] configResIds = new int[]{
com.android.internal.R.integer.config_bluetooth_idle_cur_ma,
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 56263fb924ea..7c7c7b8fa51d 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -47,7 +47,7 @@ public final class PowerStats {
private static final BatteryStatsHistory.VarintParceler VARINT_PARCELER =
new BatteryStatsHistory.VarintParceler();
- private static final byte PARCEL_FORMAT_VERSION = 1;
+ private static final byte PARCEL_FORMAT_VERSION = 2;
private static final int PARCEL_FORMAT_VERSION_MASK = 0x000000FF;
private static final int PARCEL_FORMAT_VERSION_SHIFT =
@@ -57,7 +57,12 @@ public final class PowerStats {
Integer.numberOfTrailingZeros(STATS_ARRAY_LENGTH_MASK);
public static final int MAX_STATS_ARRAY_LENGTH =
(1 << Integer.bitCount(STATS_ARRAY_LENGTH_MASK)) - 1;
- private static final int UID_STATS_ARRAY_LENGTH_MASK = 0x00FF0000;
+ private static final int STATE_STATS_ARRAY_LENGTH_MASK = 0x00FF0000;
+ private static final int STATE_STATS_ARRAY_LENGTH_SHIFT =
+ Integer.numberOfTrailingZeros(STATE_STATS_ARRAY_LENGTH_MASK);
+ public static final int MAX_STATE_STATS_ARRAY_LENGTH =
+ (1 << Integer.bitCount(STATE_STATS_ARRAY_LENGTH_MASK)) - 1;
+ private static final int UID_STATS_ARRAY_LENGTH_MASK = 0xFF000000;
private static final int UID_STATS_ARRAY_LENGTH_SHIFT =
Integer.numberOfTrailingZeros(UID_STATS_ARRAY_LENGTH_MASK);
public static final int MAX_UID_STATS_ARRAY_LENGTH =
@@ -74,6 +79,10 @@ public final class PowerStats {
private static final String XML_ATTR_ID = "id";
private static final String XML_ATTR_NAME = "name";
private static final String XML_ATTR_STATS_ARRAY_LENGTH = "stats-array-length";
+ private static final String XML_TAG_STATE = "state";
+ private static final String XML_ATTR_STATE_KEY = "key";
+ private static final String XML_ATTR_STATE_LABEL = "label";
+ private static final String XML_ATTR_STATE_STATS_ARRAY_LENGTH = "state-stats-array-length";
private static final String XML_ATTR_UID_STATS_ARRAY_LENGTH = "uid-stats-array-length";
private static final String XML_TAG_EXTRAS = "extras";
@@ -85,7 +94,24 @@ public final class PowerStats {
public final int powerComponentId;
public final String name;
+ /**
+ * Stats for the power component, such as the total usage time.
+ */
public final int statsArrayLength;
+
+ /**
+ * Map of device state codes to their corresponding human-readable labels.
+ */
+ public final SparseArray<String> stateLabels;
+
+ /**
+ * Stats for a specific state of the power component, e.g. "mobile radio in the 5G mode"
+ */
+ public final int stateStatsArrayLength;
+
+ /**
+ * Stats for the usage of this power component by a specific UID (app)
+ */
public final int uidStatsArrayLength;
/**
@@ -95,17 +121,25 @@ public final class PowerStats {
public final PersistableBundle extras;
public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId,
- int statsArrayLength, int uidStatsArrayLength, @NonNull PersistableBundle extras) {
+ int statsArrayLength, @Nullable SparseArray<String> stateLabels,
+ int stateStatsArrayLength, int uidStatsArrayLength,
+ @NonNull PersistableBundle extras) {
this(powerComponentId, BatteryConsumer.powerComponentIdToString(powerComponentId),
- statsArrayLength, uidStatsArrayLength, extras);
+ statsArrayLength, stateLabels, stateStatsArrayLength, uidStatsArrayLength,
+ extras);
}
public Descriptor(int customPowerComponentId, String name, int statsArrayLength,
+ @Nullable SparseArray<String> stateLabels, int stateStatsArrayLength,
int uidStatsArrayLength, PersistableBundle extras) {
if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) {
throw new IllegalArgumentException(
"statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH);
}
+ if (stateStatsArrayLength > MAX_STATE_STATS_ARRAY_LENGTH) {
+ throw new IllegalArgumentException(
+ "stateStatsArrayLength is too high. Max = " + MAX_STATE_STATS_ARRAY_LENGTH);
+ }
if (uidStatsArrayLength > MAX_UID_STATS_ARRAY_LENGTH) {
throw new IllegalArgumentException(
"uidStatsArrayLength is too high. Max = " + MAX_UID_STATS_ARRAY_LENGTH);
@@ -113,11 +147,25 @@ public final class PowerStats {
this.powerComponentId = customPowerComponentId;
this.name = name;
this.statsArrayLength = statsArrayLength;
+ this.stateLabels = stateLabels != null ? stateLabels : new SparseArray<>();
+ this.stateStatsArrayLength = stateStatsArrayLength;
this.uidStatsArrayLength = uidStatsArrayLength;
this.extras = extras;
}
/**
+ * Returns the label associated with the give state key, e.g. "5G-high" for the
+ * state of Mobile Radio representing the 5G mode and high signal power.
+ */
+ public String getStateLabel(int key) {
+ String label = stateLabels.get(key);
+ if (label != null) {
+ return label;
+ }
+ return name + "-" + Integer.toHexString(key);
+ }
+
+ /**
* Writes the Descriptor into the parcel.
*/
public void writeSummaryToParcel(Parcel parcel) {
@@ -125,11 +173,18 @@ public final class PowerStats {
& PARCEL_FORMAT_VERSION_MASK)
| ((statsArrayLength << STATS_ARRAY_LENGTH_SHIFT)
& STATS_ARRAY_LENGTH_MASK)
+ | ((stateStatsArrayLength << STATE_STATS_ARRAY_LENGTH_SHIFT)
+ & STATE_STATS_ARRAY_LENGTH_MASK)
| ((uidStatsArrayLength << UID_STATS_ARRAY_LENGTH_SHIFT)
& UID_STATS_ARRAY_LENGTH_MASK);
parcel.writeInt(firstWord);
parcel.writeInt(powerComponentId);
parcel.writeString(name);
+ parcel.writeInt(stateLabels.size());
+ for (int i = 0, size = stateLabels.size(); i < size; i++) {
+ parcel.writeInt(stateLabels.keyAt(i));
+ parcel.writeString(stateLabels.valueAt(i));
+ }
extras.writeToParcel(parcel, 0);
}
@@ -148,13 +203,22 @@ public final class PowerStats {
}
int statsArrayLength =
(firstWord & STATS_ARRAY_LENGTH_MASK) >>> STATS_ARRAY_LENGTH_SHIFT;
+ int stateStatsArrayLength =
+ (firstWord & STATE_STATS_ARRAY_LENGTH_MASK) >>> STATE_STATS_ARRAY_LENGTH_SHIFT;
int uidStatsArrayLength =
(firstWord & UID_STATS_ARRAY_LENGTH_MASK) >>> UID_STATS_ARRAY_LENGTH_SHIFT;
int powerComponentId = parcel.readInt();
String name = parcel.readString();
+ int stateLabelCount = parcel.readInt();
+ SparseArray<String> stateLabels = new SparseArray<>(stateLabelCount);
+ for (int i = stateLabelCount; i > 0; i--) {
+ int key = parcel.readInt();
+ String label = parcel.readString();
+ stateLabels.put(key, label);
+ }
PersistableBundle extras = parcel.readPersistableBundle();
- return new Descriptor(powerComponentId, name, statsArrayLength, uidStatsArrayLength,
- extras);
+ return new Descriptor(powerComponentId, name, statsArrayLength, stateLabels,
+ stateStatsArrayLength, uidStatsArrayLength, extras);
}
@Override
@@ -163,11 +227,13 @@ public final class PowerStats {
if (!(o instanceof Descriptor)) return false;
Descriptor that = (Descriptor) o;
return powerComponentId == that.powerComponentId
- && statsArrayLength == that.statsArrayLength
- && uidStatsArrayLength == that.uidStatsArrayLength
- && Objects.equals(name, that.name)
- && extras.size() == that.extras.size() // Unparcel the Parcel if not yet
- && Bundle.kindofEquals(extras,
+ && statsArrayLength == that.statsArrayLength
+ && stateLabels.contentEquals(that.stateLabels)
+ && stateStatsArrayLength == that.stateStatsArrayLength
+ && uidStatsArrayLength == that.uidStatsArrayLength
+ && Objects.equals(name, that.name)
+ && extras.size() == that.extras.size() // Unparcel the Parcel if not yet
+ && Bundle.kindofEquals(extras,
that.extras); // Since the Parcel is now unparceled, do a deep comparison
}
@@ -179,7 +245,14 @@ public final class PowerStats {
serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
serializer.attribute(null, XML_ATTR_NAME, name);
serializer.attributeInt(null, XML_ATTR_STATS_ARRAY_LENGTH, statsArrayLength);
+ serializer.attributeInt(null, XML_ATTR_STATE_STATS_ARRAY_LENGTH, stateStatsArrayLength);
serializer.attributeInt(null, XML_ATTR_UID_STATS_ARRAY_LENGTH, uidStatsArrayLength);
+ for (int i = stateLabels.size() - 1; i >= 0; i--) {
+ serializer.startTag(null, XML_TAG_STATE);
+ serializer.attributeInt(null, XML_ATTR_STATE_KEY, stateLabels.keyAt(i));
+ serializer.attribute(null, XML_ATTR_STATE_LABEL, stateLabels.valueAt(i));
+ serializer.endTag(null, XML_TAG_STATE);
+ }
try {
serializer.startTag(null, XML_TAG_EXTRAS);
extras.saveToXml(serializer);
@@ -199,6 +272,8 @@ public final class PowerStats {
int powerComponentId = -1;
String name = null;
int statsArrayLength = 0;
+ SparseArray<String> stateLabels = new SparseArray<>();
+ int stateStatsArrayLength = 0;
int uidStatsArrayLength = 0;
PersistableBundle extras = null;
int eventType = parser.getEventType();
@@ -212,9 +287,16 @@ public final class PowerStats {
name = parser.getAttributeValue(null, XML_ATTR_NAME);
statsArrayLength = parser.getAttributeInt(null,
XML_ATTR_STATS_ARRAY_LENGTH);
+ stateStatsArrayLength = parser.getAttributeInt(null,
+ XML_ATTR_STATE_STATS_ARRAY_LENGTH);
uidStatsArrayLength = parser.getAttributeInt(null,
XML_ATTR_UID_STATS_ARRAY_LENGTH);
break;
+ case XML_TAG_STATE:
+ int value = parser.getAttributeInt(null, XML_ATTR_STATE_KEY);
+ String label = parser.getAttributeValue(null, XML_ATTR_STATE_LABEL);
+ stateLabels.put(value, label);
+ break;
case XML_TAG_EXTRAS:
extras = PersistableBundle.restoreFromXml(parser);
break;
@@ -225,11 +307,11 @@ public final class PowerStats {
if (powerComponentId == -1) {
return null;
} else if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
- return new Descriptor(powerComponentId, name, statsArrayLength, uidStatsArrayLength,
- extras);
+ return new Descriptor(powerComponentId, name, statsArrayLength,
+ stateLabels, stateStatsArrayLength, uidStatsArrayLength, extras);
} else if (powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT) {
- return new Descriptor(powerComponentId, statsArrayLength, uidStatsArrayLength,
- extras);
+ return new Descriptor(powerComponentId, statsArrayLength, stateLabels,
+ stateStatsArrayLength, uidStatsArrayLength, extras);
} else {
Slog.e(TAG, "Unrecognized power component: " + powerComponentId);
return null;
@@ -247,12 +329,14 @@ public final class PowerStats {
extras.size(); // Unparcel
}
return "PowerStats.Descriptor{"
- + "powerComponentId=" + powerComponentId
- + ", name='" + name + '\''
- + ", statsArrayLength=" + statsArrayLength
- + ", uidStatsArrayLength=" + uidStatsArrayLength
- + ", extras=" + extras
- + '}';
+ + "powerComponentId=" + powerComponentId
+ + ", name='" + name + '\''
+ + ", statsArrayLength=" + statsArrayLength
+ + ", stateStatsArrayLength=" + stateStatsArrayLength
+ + ", stateLabels=" + stateLabels
+ + ", uidStatsArrayLength=" + uidStatsArrayLength
+ + ", extras=" + extras
+ + '}';
}
}
@@ -293,6 +377,12 @@ public final class PowerStats {
public long[] stats;
/**
+ * Device-wide mode stats, used when the power component can operate in different modes,
+ * e.g. RATs such as LTE and 5G.
+ */
+ public final SparseArray<long[]> stateStats = new SparseArray<>();
+
+ /**
* Per-UID CPU stats.
*/
public final SparseArray<long[]> uidStats = new SparseArray<>();
@@ -313,6 +403,15 @@ public final class PowerStats {
parcel.writeInt(descriptor.powerComponentId);
parcel.writeLong(durationMs);
VARINT_PARCELER.writeLongArray(parcel, stats);
+
+ if (descriptor.stateStatsArrayLength != 0) {
+ parcel.writeInt(stateStats.size());
+ for (int i = 0; i < stateStats.size(); i++) {
+ parcel.writeInt(stateStats.keyAt(i));
+ VARINT_PARCELER.writeLongArray(parcel, stateStats.valueAt(i));
+ }
+ }
+
parcel.writeInt(uidStats.size());
for (int i = 0; i < uidStats.size(); i++) {
parcel.writeInt(uidStats.keyAt(i));
@@ -347,6 +446,17 @@ public final class PowerStats {
stats.durationMs = parcel.readLong();
stats.stats = new long[descriptor.statsArrayLength];
VARINT_PARCELER.readLongArray(parcel, stats.stats);
+
+ if (descriptor.stateStatsArrayLength != 0) {
+ int count = parcel.readInt();
+ for (int i = 0; i < count; i++) {
+ int state = parcel.readInt();
+ long[] stateStats = new long[descriptor.stateStatsArrayLength];
+ VARINT_PARCELER.readLongArray(parcel, stateStats);
+ stats.stateStats.put(state, stateStats);
+ }
+ }
+
int uidCount = parcel.readInt();
for (int i = 0; i < uidCount; i++) {
int uid = parcel.readInt();
@@ -376,6 +486,14 @@ public final class PowerStats {
if (stats.length > 0) {
sb.append("=").append(Arrays.toString(stats));
}
+ if (descriptor.stateStatsArrayLength != 0) {
+ for (int i = 0; i < stateStats.size(); i++) {
+ sb.append(" [");
+ sb.append(descriptor.getStateLabel(stateStats.keyAt(i)));
+ sb.append("]=");
+ sb.append(Arrays.toString(stateStats.valueAt(i)));
+ }
+ }
for (int i = 0; i < uidStats.size(); i++) {
sb.append(uidPrefix)
.append(UserHandle.formatUid(uidStats.keyAt(i)))
@@ -391,6 +509,18 @@ public final class PowerStats {
pw.println("PowerStats: " + descriptor.name + " (" + descriptor.powerComponentId + ')');
pw.increaseIndent();
pw.print("duration", durationMs).println();
+ if (descriptor.statsArrayLength != 0) {
+ pw.print("stats", Arrays.toString(stats)).println();
+ }
+ if (descriptor.stateStatsArrayLength != 0) {
+ for (int i = 0; i < stateStats.size(); i++) {
+ pw.print("state ");
+ pw.print(descriptor.getStateLabel(stateStats.keyAt(i)));
+ pw.print(": ");
+ pw.print(Arrays.toString(stateStats.valueAt(i)));
+ pw.println();
+ }
+ }
for (int i = 0; i < uidStats.size(); i++) {
pw.print("UID ");
pw.print(uidStats.keyAt(i));
diff --git a/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig b/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig
index 89db1cb74c01..ea9abdbc4388 100644
--- a/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig
+++ b/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig
@@ -1,5 +1,4 @@
package: "com.android.internal.pm.pkg.component.flags"
-container: "system"
flag {
name: "enable_per_process_use_embedded_dex_attr"
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 e12becd87e12..97ce96ec30f6 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -238,6 +238,7 @@ public class ParsingPackageUtils {
*/
public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
public static final int PARSE_APK_IN_APEX = 1 << 9;
+ public static final int PARSE_APEX = 1 << 10;
public static final int PARSE_CHATTY = 1 << 31;
@@ -339,6 +340,9 @@ public class ParsingPackageUtils {
if ((flags & PARSE_APK_IN_APEX) != 0) {
liteParseFlags |= PARSE_APK_IN_APEX;
}
+ if ((flags & PARSE_APEX) != 0) {
+ liteParseFlags |= PARSE_APEX;
+ }
final ParseResult<PackageLite> liteResult =
ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, liteParseFlags);
if (liteResult.isError()) {
@@ -530,7 +534,7 @@ public class ParsingPackageUtils {
afterParseBaseApplication(pkg);
- final ParseResult<ParsingPackage> result = validateBaseApkTags(input, pkg);
+ final ParseResult<ParsingPackage> result = validateBaseApkTags(input, pkg, flags);
if (result.isError()) {
return result;
}
@@ -1012,10 +1016,11 @@ public class ParsingPackageUtils {
}
}
- return validateBaseApkTags(input, pkg);
+ return validateBaseApkTags(input, pkg, flags);
}
- private ParseResult<ParsingPackage> validateBaseApkTags(ParseInput input, ParsingPackage pkg) {
+ private ParseResult<ParsingPackage> validateBaseApkTags(ParseInput input, ParsingPackage pkg,
+ int flags) {
if (!ParsedAttributionUtils.isCombinationValid(pkg.getAttributions())) {
return input.error(
INSTALL_PARSE_FAILED_BAD_MANIFEST,
@@ -1047,6 +1052,16 @@ public class ParsingPackageUtils {
adjustPackageToBeUnresizeableAndUnpipable(pkg);
}
+ // An Apex package shouldn't have permission declarations
+ final boolean isApex = (flags & PARSE_APEX) != 0;
+ if (isApex && !pkg.getPermissions().isEmpty()) {
+ return input.error(
+ INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ pkg.getPackageName()
+ + " is an APEX package and shouldn't declare permissions."
+ );
+ }
+
return input.success(pkg);
}
diff --git a/core/java/com/android/internal/power/ModemPowerProfile.java b/core/java/com/android/internal/power/ModemPowerProfile.java
index b15c10e6ba20..64d3139561b6 100644
--- a/core/java/com/android/internal/power/ModemPowerProfile.java
+++ b/core/java/com/android/internal/power/ModemPowerProfile.java
@@ -17,14 +17,16 @@
package com.android.internal.power;
import android.annotation.IntDef;
-import android.content.res.XmlResourceParser;
+import android.os.BatteryStats;
import android.telephony.ModemActivityInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseDoubleArray;
+import com.android.internal.os.PowerProfile;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -95,6 +97,8 @@ public class ModemPowerProfile {
*/
public static final int MODEM_DRAIN_TYPE_TX = 0x3000_0000;
+ private static final int IGNORE = -1;
+
@IntDef(prefix = {"MODEM_DRAIN_TYPE_"}, value = {
MODEM_DRAIN_TYPE_SLEEP,
MODEM_DRAIN_TYPE_IDLE,
@@ -256,7 +260,7 @@ public class ModemPowerProfile {
/**
* Generates a ModemPowerProfile object from the <modem /> element of a power_profile.xml
*/
- public void parseFromXml(XmlResourceParser parser) throws IOException,
+ public void parseFromXml(XmlPullParser parser) throws IOException,
XmlPullParserException {
final int depth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, depth)) {
@@ -286,7 +290,7 @@ public class ModemPowerProfile {
}
/** Parse the <active /> XML element */
- private void parseActivePowerConstantsFromXml(XmlResourceParser parser)
+ private void parseActivePowerConstantsFromXml(XmlPullParser parser)
throws IOException, XmlPullParserException {
// Parse attributes to get the type of active modem usage the power constants are for.
final int ratType;
@@ -339,7 +343,7 @@ public class ModemPowerProfile {
}
}
- private static int getTypeFromAttribute(XmlResourceParser parser, String attr,
+ private static int getTypeFromAttribute(XmlPullParser parser, String attr,
SparseArray<String> names) {
final String value = XmlUtils.readStringAttribute(parser, attr);
if (value == null) {
@@ -382,6 +386,84 @@ public class ModemPowerProfile {
}
}
+ public static long getAverageBatteryDrainKey(@ModemDrainType int drainType,
+ @BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange,
+ int txLevel) {
+ long key = PowerProfile.SUBSYSTEM_MODEM;
+
+ // Attach Modem drain type to the key if specified.
+ if (drainType != IGNORE) {
+ key |= drainType;
+ }
+
+ // Attach RadioAccessTechnology to the key if specified.
+ switch (rat) {
+ case IGNORE:
+ // do nothing
+ break;
+ case BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER:
+ key |= MODEM_RAT_TYPE_DEFAULT;
+ break;
+ case BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE:
+ key |= MODEM_RAT_TYPE_LTE;
+ break;
+ case BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR:
+ key |= MODEM_RAT_TYPE_NR;
+ break;
+ default:
+ Log.w(TAG, "Unexpected RadioAccessTechnology : " + rat);
+ }
+
+ // Attach NR Frequency Range to the key if specified.
+ switch (freqRange) {
+ case IGNORE:
+ // do nothing
+ break;
+ case ServiceState.FREQUENCY_RANGE_UNKNOWN:
+ key |= MODEM_NR_FREQUENCY_RANGE_DEFAULT;
+ break;
+ case ServiceState.FREQUENCY_RANGE_LOW:
+ key |= MODEM_NR_FREQUENCY_RANGE_LOW;
+ break;
+ case ServiceState.FREQUENCY_RANGE_MID:
+ key |= MODEM_NR_FREQUENCY_RANGE_MID;
+ break;
+ case ServiceState.FREQUENCY_RANGE_HIGH:
+ key |= MODEM_NR_FREQUENCY_RANGE_HIGH;
+ break;
+ case ServiceState.FREQUENCY_RANGE_MMWAVE:
+ key |= MODEM_NR_FREQUENCY_RANGE_MMWAVE;
+ break;
+ default:
+ Log.w(TAG, "Unexpected NR frequency range : " + freqRange);
+ }
+
+ // Attach transmission level to the key if specified.
+ switch (txLevel) {
+ case IGNORE:
+ // do nothing
+ break;
+ case 0:
+ key |= MODEM_TX_LEVEL_0;
+ break;
+ case 1:
+ key |= MODEM_TX_LEVEL_1;
+ break;
+ case 2:
+ key |= MODEM_TX_LEVEL_2;
+ break;
+ case 3:
+ key |= MODEM_TX_LEVEL_3;
+ break;
+ case 4:
+ key |= MODEM_TX_LEVEL_4;
+ break;
+ default:
+ Log.w(TAG, "Unexpected transmission level : " + txLevel);
+ }
+ return key;
+ }
+
/**
* Returns the average battery drain in milli-amps of the modem for a given drain type.
* Returns {@link Double.NaN} if a suitable value is not found for the given key.
@@ -444,6 +526,7 @@ public class ModemPowerProfile {
}
return sb.toString();
}
+
private static void appendFieldToString(StringBuilder sb, String fieldName,
SparseArray<String> names, int key) {
sb.append(fieldName);
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 9f3ce8163bf3..fca4e91a8ac3 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -53,6 +53,7 @@ import android.tracing.perfetto.InitArguments;
import android.tracing.perfetto.Producer;
import android.tracing.perfetto.TracingContext;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.LongArray;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
@@ -67,6 +68,7 @@ import com.android.internal.protolog.common.LogLevel;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
@@ -166,76 +168,90 @@ public class PerfettoProtoLogImpl implements IProtoLog {
}
mDataSource.trace(ctx -> {
- final ProtoOutputStream os = ctx.newTracePacket();
-
- os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+ try {
+ final ProtoOutputStream os = ctx.newTracePacket();
- final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
- while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- if (pis.getFieldNumber() == (int) MESSAGES) {
- final long inMessageToken = pis.start(MESSAGES);
- final long outMessagesToken = os.start(MESSAGES);
+ os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
- while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (pis.getFieldNumber()) {
- case (int) MessageData.MESSAGE_ID:
- os.write(MessageData.MESSAGE_ID,
- pis.readLong(MessageData.MESSAGE_ID));
- break;
- case (int) MESSAGE:
- os.write(MESSAGE, pis.readString(MESSAGE));
- break;
- case (int) LEVEL:
- os.write(LEVEL, pis.readInt(LEVEL));
- break;
- case (int) GROUP_ID:
- os.write(GROUP_ID, pis.readInt(GROUP_ID));
- break;
- default:
- throw new RuntimeException(
- "Unexpected field id " + pis.getFieldNumber());
- }
+ final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (pis.getFieldNumber() == (int) MESSAGES) {
+ writeViewerConfigMessage(pis, os);
}
- pis.end(inMessageToken);
- os.end(outMessagesToken);
+ if (pis.getFieldNumber() == (int) GROUPS) {
+ writeViewerConfigGroup(pis, os);
+ }
}
- if (pis.getFieldNumber() == (int) GROUPS) {
- final long inGroupToken = pis.start(GROUPS);
- final long outGroupToken = os.start(GROUPS);
+ os.end(outProtologViewerConfigToken);
- while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (pis.getFieldNumber()) {
- case (int) ID:
- int id = pis.readInt(ID);
- os.write(ID, id);
- break;
- case (int) NAME:
- String name = pis.readString(NAME);
- os.write(NAME, name);
- break;
- case (int) TAG:
- String tag = pis.readString(TAG);
- os.write(TAG, tag);
- break;
- default:
- throw new RuntimeException(
- "Unexpected field id " + pis.getFieldNumber());
- }
- }
+ ctx.flush();
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e);
+ }
+ });
- pis.end(inGroupToken);
- os.end(outGroupToken);
- }
+ mDataSource.flush();
+ }
+
+ private static void writeViewerConfigGroup(
+ ProtoInputStream pis, ProtoOutputStream os) throws IOException {
+ final long inGroupToken = pis.start(GROUPS);
+ final long outGroupToken = os.start(GROUPS);
+
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (pis.getFieldNumber()) {
+ case (int) ID:
+ int id = pis.readInt(ID);
+ os.write(ID, id);
+ break;
+ case (int) NAME:
+ String name = pis.readString(NAME);
+ os.write(NAME, name);
+ break;
+ case (int) TAG:
+ String tag = pis.readString(TAG);
+ os.write(TAG, tag);
+ break;
+ default:
+ throw new RuntimeException(
+ "Unexpected field id " + pis.getFieldNumber());
}
+ }
- os.end(outProtologViewerConfigToken);
+ pis.end(inGroupToken);
+ os.end(outGroupToken);
+ }
- ctx.flush();
- });
+ private static void writeViewerConfigMessage(
+ ProtoInputStream pis, ProtoOutputStream os) throws IOException {
+ final long inMessageToken = pis.start(MESSAGES);
+ final long outMessagesToken = os.start(MESSAGES);
+
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (pis.getFieldNumber()) {
+ case (int) MessageData.MESSAGE_ID:
+ os.write(MessageData.MESSAGE_ID,
+ pis.readLong(MessageData.MESSAGE_ID));
+ break;
+ case (int) MESSAGE:
+ os.write(MESSAGE, pis.readString(MESSAGE));
+ break;
+ case (int) LEVEL:
+ os.write(LEVEL, pis.readInt(LEVEL));
+ break;
+ case (int) GROUP_ID:
+ os.write(GROUP_ID, pis.readInt(GROUP_ID));
+ break;
+ default:
+ throw new RuntimeException(
+ "Unexpected field id " + pis.getFieldNumber());
+ }
+ }
- mDataSource.flush();
+ pis.end(inMessageToken);
+ os.end(outMessagesToken);
}
private void logToLogcat(String tag, LogLevel level, long messageHash,
@@ -423,7 +439,7 @@ public class PerfettoProtoLogImpl implements IProtoLog {
return sw.toString();
}
- private int internStacktraceString(TracingContext<ProtoLogDataSource.Instance,
+ private int internStacktraceString(TracingContext<
ProtoLogDataSource.TlsState,
ProtoLogDataSource.IncrementalState> ctx,
String stacktrace) {
@@ -433,9 +449,7 @@ public class PerfettoProtoLogImpl implements IProtoLog {
}
private int internStringArg(
- TracingContext<ProtoLogDataSource.Instance,
- ProtoLogDataSource.TlsState,
- ProtoLogDataSource.IncrementalState> ctx,
+ TracingContext<ProtoLogDataSource.TlsState, ProtoLogDataSource.IncrementalState> ctx,
String string
) {
final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState();
@@ -444,9 +458,7 @@ public class PerfettoProtoLogImpl implements IProtoLog {
}
private int internString(
- TracingContext<ProtoLogDataSource.Instance,
- ProtoLogDataSource.TlsState,
- ProtoLogDataSource.IncrementalState> ctx,
+ TracingContext<ProtoLogDataSource.TlsState, ProtoLogDataSource.IncrementalState> ctx,
Map<String, Integer> internMap,
long fieldId,
String string
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 9c63d0dd746a..bd654fa0095c 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -1212,6 +1212,10 @@ public class ConversationLayout extends FrameLayout
}
}
}
+ if (android.app.Flags.cleanUpSpansAndNewLines() && conversationText != null) {
+ // remove formatting from title.
+ conversationText = conversationText.toString();
+ }
if (conversationIcon == null) {
conversationIcon = largeIcon;
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index f8f104943c61..97a2d3beda90 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -21,6 +21,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
+import android.app.Flags;
import android.app.Person;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -200,6 +201,10 @@ public class MessagingGroup extends NotificationOptimizedLinearLayout implements
if (nameOverride == null) {
nameOverride = sender.getName();
}
+ if (Flags.cleanUpSpansAndNewLines() && nameOverride != null) {
+ // remove formatting from sender name
+ nameOverride = nameOverride.toString();
+ }
mSenderName = nameOverride;
if (mSingleLine && !TextUtils.isEmpty(nameOverride)) {
nameOverride = mContext.getResources().getString(
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
index 9525605a6a8c..30d9ea19be39 100644
--- a/core/jni/android_os_MessageQueue.cpp
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -225,7 +225,7 @@ static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
-static void android_os_MessageQueue_nativeWake(jlong ptr) {
+static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index 25ff853ae7e4..5c7b470b6520 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -45,12 +45,6 @@ static struct {
static struct {
jclass clazz;
jmethodID init;
- jmethodID getAndClearAllPendingTracePackets;
-} gTracingContextClassInfo;
-
-static struct {
- jclass clazz;
- jmethodID init;
} gCreateTlsStateArgsClassInfo;
static struct {
@@ -68,32 +62,10 @@ struct IncrementalState {
jobject jobj;
};
-static void traceAllPendingPackets(JNIEnv* env, jobject jCtx, PerfettoDsTracerIterator* ctx) {
- jobjectArray packets =
- (jobjectArray)env
- ->CallObjectMethod(jCtx,
- gTracingContextClassInfo.getAndClearAllPendingTracePackets);
- if (env->ExceptionOccurred()) {
- env->ExceptionDescribe();
- env->ExceptionClear();
-
- LOG_ALWAYS_FATAL("Failed to call java context finalize method");
- }
-
- int packets_count = env->GetArrayLength(packets);
- for (int i = 0; i < packets_count; i++) {
- jbyteArray packet_proto_buffer = (jbyteArray)env->GetObjectArrayElement(packets, i);
-
- jbyte* raw_proto_buffer = env->GetByteArrayElements(packet_proto_buffer, 0);
- int buffer_size = env->GetArrayLength(packet_proto_buffer);
-
- struct PerfettoDsRootTracePacket trace_packet;
- PerfettoDsTracerPacketBegin(ctx, &trace_packet);
- PerfettoPbMsgAppendBytes(&trace_packet.msg.msg, (const uint8_t*)raw_proto_buffer,
- buffer_size);
- PerfettoDsTracerPacketEnd(ctx, &trace_packet);
- }
-}
+// In a single thread there can be only one trace point active across all data source, so we can use
+// a single global thread_local variable to keep track of the active tracer iterator.
+thread_local static bool gInIteration;
+thread_local static struct PerfettoDsTracerIterator gIterator;
PerfettoDataSource::PerfettoDataSource(JNIEnv* env, jobject javaDataSource,
std::string dataSourceName)
@@ -164,44 +136,89 @@ jobject PerfettoDataSource::createIncrementalStateGlobalRef(JNIEnv* env,
return env->NewGlobalRef(incrementalState.get());
}
-void PerfettoDataSource::trace(JNIEnv* env, jobject traceFunction) {
- PERFETTO_DS_TRACE(dataSource, ctx) {
- ALOG(LOG_DEBUG, LOG_TAG, "\tin native trace callback function %p", this);
- TlsState* tls_state =
- reinterpret_cast<TlsState*>(PerfettoDsGetCustomTls(&dataSource, &ctx));
- IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(
- PerfettoDsGetIncrementalState(&dataSource, &ctx));
+bool PerfettoDataSource::TraceIterateBegin() {
+ if (gInIteration) {
+ return false;
+ }
- ALOG(LOG_DEBUG, LOG_TAG, "\t tls_state = %p", tls_state);
- ALOG(LOG_DEBUG, LOG_TAG, "\t incr_state = %p", incr_state);
+ gIterator = PerfettoDsTraceIterateBegin(&dataSource);
- ALOG(LOG_DEBUG, LOG_TAG, "\t tls_state->jobj = %p", tls_state->jobj);
- ALOG(LOG_DEBUG, LOG_TAG, "\t incr_state->jobj = %p", incr_state->jobj);
+ if (gIterator.impl.tracer == nullptr) {
+ return false;
+ }
- ScopedLocalRef<jobject> jCtx(env,
- env->NewObject(gTracingContextClassInfo.clazz,
- gTracingContextClassInfo.init, &ctx,
- tls_state->jobj, incr_state->jobj));
+ gInIteration = true;
+ return true;
+}
- ALOG(LOG_DEBUG, LOG_TAG, "\t jCtx = %p", jCtx.get());
+bool PerfettoDataSource::TraceIterateNext() {
+ if (!gInIteration) {
+ LOG_ALWAYS_FATAL("Tried calling TraceIterateNext outside of a tracer iteration.");
+ return false;
+ }
- jclass objclass = env->GetObjectClass(traceFunction);
- jmethodID method =
- env->GetMethodID(objclass, "trace", "(Landroid/tracing/perfetto/TracingContext;)V");
- if (method == 0) {
- LOG_ALWAYS_FATAL("Failed to get method id");
- }
+ PerfettoDsTraceIterateNext(&dataSource, &gIterator);
- env->ExceptionClear();
+ if (gIterator.impl.tracer == nullptr) {
+ // Reached end of iterator. No more datasource instances.
+ gInIteration = false;
+ return false;
+ }
+
+ return true;
+}
+
+void PerfettoDataSource::TraceIterateBreak() {
+ if (!gInIteration) {
+ return;
+ }
- env->CallVoidMethod(traceFunction, method, jCtx.get());
- if (env->ExceptionOccurred()) {
- env->ExceptionDescribe();
- env->ExceptionClear();
- LOG_ALWAYS_FATAL("Failed to call java trace method");
- }
+ PerfettoDsTraceIterateBreak(&dataSource, &gIterator);
+ gInIteration = false;
+}
- traceAllPendingPackets(env, jCtx.get(), &ctx);
+jobject PerfettoDataSource::GetCustomTls() {
+ if (!gInIteration) {
+ LOG_ALWAYS_FATAL("Tried getting CustomTls outside of a tracer iteration.");
+ return nullptr;
+ }
+
+ TlsState* tls_state =
+ reinterpret_cast<TlsState*>(PerfettoDsGetCustomTls(&dataSource, &gIterator));
+
+ return tls_state->jobj;
+}
+
+jobject PerfettoDataSource::GetIncrementalState() {
+ if (!gInIteration) {
+ LOG_ALWAYS_FATAL("Tried getting IncrementalState outside of a tracer iteration.");
+ return nullptr;
+ }
+
+ IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(
+ PerfettoDsGetIncrementalState(&dataSource, &gIterator));
+
+ return incr_state->jobj;
+}
+
+void PerfettoDataSource::WritePackets(JNIEnv* env, jobjectArray packets) {
+ if (!gInIteration) {
+ LOG_ALWAYS_FATAL("Tried writing packets outside of a tracer iteration.");
+ return;
+ }
+
+ int packets_count = env->GetArrayLength(packets);
+ for (int i = 0; i < packets_count; i++) {
+ jbyteArray packet_proto_buffer = (jbyteArray)env->GetObjectArrayElement(packets, i);
+
+ jbyte* raw_proto_buffer = env->GetByteArrayElements(packet_proto_buffer, 0);
+ int buffer_size = env->GetArrayLength(packet_proto_buffer);
+
+ struct PerfettoDsRootTracePacket trace_packet;
+ PerfettoDsTracerPacketBegin(&gIterator, &trace_packet);
+ PerfettoPbMsgAppendBytes(&trace_packet.msg.msg, (const uint8_t*)raw_proto_buffer,
+ buffer_size);
+ PerfettoDsTracerPacketEnd(&gIterator, &trace_packet);
}
}
@@ -218,9 +235,7 @@ PerfettoDataSource::~PerfettoDataSource() {
jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring name) {
const char* nativeString = env->GetStringUTFChars(name, 0);
- ALOG(LOG_DEBUG, LOG_TAG, "nativeCreate(%p, %s)", javaDataSource, nativeString);
PerfettoDataSource* dataSource = new PerfettoDataSource(env, javaDataSource, nativeString);
- ALOG(LOG_DEBUG, LOG_TAG, "\tdatasource* = %p", dataSource);
env->ReleaseStringUTFChars(name, nativeString);
dataSource->incStrong((void*)nativeCreate);
@@ -229,39 +244,27 @@ jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring na
}
void nativeDestroy(void* ptr) {
- ALOG(LOG_DEBUG, LOG_TAG, "nativeCreate(%p)", ptr);
PerfettoDataSource* dataSource = reinterpret_cast<PerfettoDataSource*>(ptr);
dataSource->decStrong((void*)nativeCreate);
}
static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
- ALOG(LOG_DEBUG, LOG_TAG, "nativeGetFinalizer()");
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy));
}
-void nativeTrace(JNIEnv* env, jclass clazz, jlong dataSourcePtr, jobject traceFunctionInterface) {
- ALOG(LOG_DEBUG, LOG_TAG, "nativeTrace(%p)", (void*)dataSourcePtr);
- sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
-
- datasource->trace(env, traceFunctionInterface);
-}
-
-void nativeFlush(JNIEnv* env, jclass clazz, jobject jCtx, jlong ctxPtr) {
- ALOG(LOG_DEBUG, LOG_TAG, "nativeFlush(%p, %p)", jCtx, (void*)ctxPtr);
- auto* ctx = reinterpret_cast<struct PerfettoDsTracerIterator*>(ctxPtr);
- traceAllPendingPackets(env, jCtx, ctx);
- PerfettoDsTracerFlush(ctx, nullptr, nullptr);
+void nativeFlush(JNIEnv* env, jclass clazz, jlong ds_ptr, jobjectArray packets) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeFlush(%p)", (void*)ds_ptr);
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ds_ptr);
+ datasource->WritePackets(env, packets);
}
void nativeFlushAll(JNIEnv* env, jclass clazz, jlong ptr) {
- ALOG(LOG_DEBUG, LOG_TAG, "nativeFlushAll(%p)", (void*)ptr);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ptr);
datasource->flushAll();
}
void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
int buffer_exhausted_policy) {
- ALOG(LOG_DEBUG, LOG_TAG, "nativeRegisterDataSource(%p)", (void*)datasource_ptr);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr);
struct PerfettoDsParams params = PerfettoDsParamsDefault();
@@ -283,10 +286,6 @@ void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
auto* datasource_instance =
new PerfettoDataSourceInstance(env, java_data_source_instance.get(), inst_id);
-
- ALOG(LOG_DEBUG, LOG_TAG, "on_setup_cb ds=%p, ds_instance=%p", datasource,
- datasource_instance);
-
return static_cast<void*>(datasource_instance);
};
@@ -299,9 +298,6 @@ void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
jobject java_tls_state = datasource->createTlsStateGlobalRef(env, inst_id);
auto* tls_state = new TlsState(java_tls_state);
-
- ALOG(LOG_DEBUG, LOG_TAG, "on_create_tls_cb ds=%p, tsl_state=%p", datasource, tls_state);
-
return static_cast<void*>(tls_state);
};
@@ -310,8 +306,6 @@ void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
TlsState* tls_state = reinterpret_cast<TlsState*>(ptr);
- ALOG(LOG_DEBUG, LOG_TAG, "on_delete_tls_cb %p", tls_state);
-
env->DeleteGlobalRef(tls_state->jobj);
delete tls_state;
};
@@ -324,9 +318,6 @@ void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
jobject java_incr_state = datasource->createIncrementalStateGlobalRef(env, inst_id);
auto* incr_state = new IncrementalState(java_incr_state);
-
- ALOG(LOG_DEBUG, LOG_TAG, "on_create_incr_cb ds=%p, incr_state=%p", datasource, incr_state);
-
return static_cast<void*>(incr_state);
};
@@ -335,8 +326,6 @@ void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(ptr);
- ALOG(LOG_DEBUG, LOG_TAG, "on_delete_incr_cb incr_state=%p", incr_state);
-
env->DeleteGlobalRef(incr_state->jobj);
delete incr_state;
};
@@ -346,9 +335,6 @@ void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
-
- ALOG(LOG_DEBUG, LOG_TAG, "on_start_cb ds_instance=%p", datasource_instance);
-
datasource_instance->onStart(env);
};
@@ -357,9 +343,6 @@ void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
-
- ALOG(LOG_DEBUG, LOG_TAG, "on_flush_cb ds_instance=%p", datasource_instance);
-
datasource_instance->onFlush(env);
};
@@ -368,9 +351,6 @@ void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
-
- ALOG(LOG_DEBUG, LOG_TAG, "on_stop_cb ds_instance=%p", datasource_instance);
-
datasource_instance->onStop(env);
};
@@ -378,18 +358,14 @@ void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
void* inst_ctx) -> void {
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
- ALOG(LOG_DEBUG, LOG_TAG, "on_destroy_cb ds_instance=%p", datasource_instance);
-
delete datasource_instance;
};
PerfettoDsRegister(&datasource->dataSource, datasource->dataSourceName.c_str(), params);
}
-jobject nativeGetPerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
+jobject nativeGetPerfettoInstanceLocked(JNIEnv* /* env */, jclass /* clazz */, jlong dataSourcePtr,
PerfettoDsInstanceIndex instance_idx) {
- ALOG(LOG_DEBUG, LOG_TAG, "nativeGetPerfettoInstanceLocked ds=%p, idx=%d", (void*)dataSourcePtr,
- instance_idx);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(
PerfettoDsImplGetInstanceLocked(datasource->dataSource.impl, instance_idx));
@@ -401,24 +377,44 @@ jobject nativeGetPerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSou
return nullptr;
}
- ALOG(LOG_DEBUG, LOG_TAG, "\tnativeGetPerfettoInstanceLocked got lock ds=%p, idx=%d",
- (void*)dataSourcePtr, instance_idx);
return datasource_instance->GetJavaDataSourceInstance();
}
-void nativeReleasePerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
+void nativeReleasePerfettoInstanceLocked(JNIEnv* /* env */, jclass /* clazz */, jlong dataSourcePtr,
PerfettoDsInstanceIndex instance_idx) {
- ALOG(LOG_DEBUG, LOG_TAG, "nativeReleasePerfettoInstanceLocked got lock ds=%p, idx=%d",
- (void*)dataSourcePtr, instance_idx);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
PerfettoDsImplReleaseInstanceLocked(datasource->dataSource.impl, instance_idx);
}
+bool nativePerfettoDsTraceIterateBegin(jlong dataSourcePtr) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+ return datasource->TraceIterateBegin();
+}
+
+bool nativePerfettoDsTraceIterateNext(jlong dataSourcePtr) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+ return datasource->TraceIterateNext();
+}
+
+void nativePerfettoDsTraceIterateBreak(jlong dataSourcePtr) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+ return datasource->TraceIterateBreak();
+}
+
+jobject nativeGetCustomTls(JNIEnv* /* env */, jclass /* clazz */, jlong dataSourcePtr) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+ return datasource->GetCustomTls();
+}
+
+jobject nativeGetIncrementalState(JNIEnv* /* env */, jclass /* clazz */, jlong dataSourcePtr) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+ return datasource->GetIncrementalState();
+}
+
const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{"nativeCreate", "(Landroid/tracing/perfetto/DataSource;Ljava/lang/String;)J",
(void*)nativeCreate},
- {"nativeTrace", "(JLandroid/tracing/perfetto/TraceFunction;)V", (void*)nativeTrace},
{"nativeFlushAll", "(J)V", (void*)nativeFlushAll},
{"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
{"nativeRegisterDataSource", "(JI)V", (void*)nativeRegisterDataSource},
@@ -426,11 +422,16 @@ const JNINativeMethod gMethods[] = {
(void*)nativeGetPerfettoInstanceLocked},
{"nativeReleasePerfettoInstanceLocked", "(JI)V",
(void*)nativeReleasePerfettoInstanceLocked},
-};
+
+ {"nativePerfettoDsTraceIterateBegin", "(J)Z", (void*)nativePerfettoDsTraceIterateBegin},
+ {"nativePerfettoDsTraceIterateNext", "(J)Z", (void*)nativePerfettoDsTraceIterateNext},
+ {"nativePerfettoDsTraceIterateBreak", "(J)V", (void*)nativePerfettoDsTraceIterateBreak}};
const JNINativeMethod gMethodsTracingContext[] = {
/* name, signature, funcPtr */
- {"nativeFlush", "(Landroid/tracing/perfetto/TracingContext;J)V", (void*)nativeFlush},
+ {"nativeFlush", "(J[[B)V", (void*)nativeFlush},
+ {"nativeGetCustomTls", "(J)Ljava/lang/Object;", (void*)nativeGetCustomTls},
+ {"nativeGetIncrementalState", "(J)Ljava/lang/Object;", (void*)nativeGetIncrementalState},
};
int register_android_tracing_PerfettoDataSource(JNIEnv* env) {
@@ -461,14 +462,6 @@ int register_android_tracing_PerfettoDataSource(JNIEnv* env) {
"(Landroid/tracing/perfetto/CreateIncrementalStateArgs;)Ljava/lang/"
"Object;");
- clazz = env->FindClass("android/tracing/perfetto/TracingContext");
- gTracingContextClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
- gTracingContextClassInfo.init = env->GetMethodID(gTracingContextClassInfo.clazz, "<init>",
- "(JLjava/lang/Object;Ljava/lang/Object;)V");
- gTracingContextClassInfo.getAndClearAllPendingTracePackets =
- env->GetMethodID(gTracingContextClassInfo.clazz, "getAndClearAllPendingTracePackets",
- "()[[B");
-
clazz = env->FindClass("android/tracing/perfetto/CreateTlsStateArgs");
gCreateTlsStateArgsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
gCreateTlsStateArgsClassInfo.init =
diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h
index 906d9f515a2b..209de29f17d6 100644
--- a/core/jni/android_tracing_PerfettoDataSource.h
+++ b/core/jni/android_tracing_PerfettoDataSource.h
@@ -46,7 +46,15 @@ public:
jobject createTlsStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id);
jobject createIncrementalStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id);
- void trace(JNIEnv* env, jobject trace_function);
+
+ bool TraceIterateBegin();
+ bool TraceIterateNext();
+ void TraceIterateBreak();
+ void WritePackets(JNIEnv* env, jobjectArray packets);
+
+ jobject GetCustomTls();
+ jobject GetIncrementalState();
+
void flushAll();
private:
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index 76b05eac82af..b3c41dfe81a1 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -55,6 +55,14 @@ static void native_setState(jlong nativePtr, jint state, jlong timestamp) {
counter->setState(state, timestamp);
}
+static void native_copyStatesFrom(jlong nativePtrTarget, jlong nativePtrSource) {
+ battery::LongArrayMultiStateCounter *counterTarget =
+ reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrTarget);
+ battery::LongArrayMultiStateCounter *counterSource =
+ reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrSource);
+ counterTarget->copyStatesFrom(*counterSource);
+}
+
static void native_setValues(jlong nativePtr, jint state, jlong longArrayContainerNativePtr) {
battery::LongArrayMultiStateCounter *counter =
reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
@@ -219,6 +227,8 @@ static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = {
// @CriticalNative
{"native_setState", "(JIJ)V", (void *)native_setState},
// @CriticalNative
+ {"native_copyStatesFrom", "(JJ)V", (void *)native_copyStatesFrom},
+ // @CriticalNative
{"native_setValues", "(JIJ)V", (void *)native_setValues},
// @CriticalNative
{"native_updateValues", "(JJJ)V", (void *)native_updateValues},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f74329903690..c694426a5aa4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2593,6 +2593,13 @@
<permission android:name="android.permission.VIBRATE_ALWAYS_ON"
android:protectionLevel="signature" />
+ <!-- Allows access to system-only haptic feedback constants.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.VIBRATE_SYSTEM_CONSTANTS"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows access to the vibrator state.
<p>Protection level: signature
@hide
@@ -3889,6 +3896,13 @@
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL"
android:protectionLevel="internal|role" />
+ <!-- Allows the holder to manage and retrieve max storage limit for admin policies. This
+ permission is only grantable on rooted devices.
+ @TestAPI
+ @hide -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT"
+ android:protectionLevel="internal" />
+
<!-- Allows an application to access EnhancedConfirmationManager.
@SystemApi
@FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled")
diff --git a/core/res/OWNERS b/core/res/OWNERS
index 4e61ff25a26b..3489cacb3c01 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -4,7 +4,6 @@ austindelgado@google.com
cinek@google.com
dsandler@android.com
dsandler@google.com
-dupin@google.com
hackbod@android.com
hackbod@google.com
ilyamaty@google.com
@@ -49,7 +48,7 @@ per-file res/values/config_display.xml = file:/services/core/java/com/android/se
# Wear
per-file res/*-watch/* = file:/WEAR_OWNERS
-# Peformance
+# Performance
per-file res/values/config.xml = file:/PERFORMANCE_OWNERS
per-file res/values/symbols.xml = file:/PERFORMANCE_OWNERS
@@ -63,3 +62,11 @@ per-file res/xml/sms_short_codes.xml = file:/platform/frameworks/opt/telephony:/
# TV Input Framework
per-file res/values/config_tv_external_input_logging.xml = file:/services/core/java/com/android/server/tv/OWNERS
+
+# SysUi Color Team
+per-file res/values/colors.xml = arteiro@google.com
+per-file res/values/attrs.xml = arteiro@google.com
+per-file res/values/styles.xml = arteiro@google.com
+per-file res/values/symbols.xml = arteiro@google.com
+per-file res/values/themes_device_defaults.xml = arteiro@google.com
+per-file res/values/styles_material.xml = arteiro@google.com \ No newline at end of file
diff --git a/core/res/res/drawable/pointer_spot_anchor_vector.xml b/core/res/res/drawable/pointer_spot_anchor_vector.xml
index 54de2aecb4ce..89990b80bd88 100644
--- a/core/res/res/drawable/pointer_spot_anchor_vector.xml
+++ b/core/res/res/drawable/pointer_spot_anchor_vector.xml
@@ -22,4 +22,8 @@
<path android:fillColor="#ADC6E7" android:pathData="M12 3c-4.963 0-9 4.038-9 9 0 4.963 4.037 9 9 9s9-4.037 9-9c0-4.962-4.037-9-9-9m0 17c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8" />
<path android:fillColor="#ADC6E7" android:pathData="M12 5c-3.859 0-7 3.14-7 7s3.141 7 7 7 7-3.141 7-7-3.141-7-7-7m0 13c-3.309 0-6-2.691-6-6s2.691-6 6-6 6 2.691 6 6-2.691 6-6 6" />
</group>
+ <path
+ android:pathData="M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0 -16m0 15a7 7 0 1 1 0 -14 7 7 0 0 1 0 14"
+ android:fillColor="#99FFFFFF"
+ android:fillType="evenOdd"/>
</vector> \ No newline at end of file
diff --git a/core/res/res/drawable/pointer_spot_hover_vector.xml b/core/res/res/drawable/pointer_spot_hover_vector.xml
index ef596c470480..4bf5fbced36d 100644
--- a/core/res/res/drawable/pointer_spot_hover_vector.xml
+++ b/core/res/res/drawable/pointer_spot_hover_vector.xml
@@ -22,4 +22,7 @@
<path android:fillColor="#ADC6E7" android:pathData="M12 3c-4.963 0-9 4.038-9 9 0 4.963 4.037 9 9 9s9-4.037 9-9c0-4.962-4.037-9-9-9m0 17c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8" />
<path android:fillColor="#ADC6E7" android:pathData="M12 7c-2.757 0-5 2.243-5 5s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5m0 9c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4" />
</group>
+ <path
+ android:pathData="M12 3.998a8.002 8.002 0 1 0 0 16.004 8.002 8.002 0 0 0 0 -16.004m0 13.004a5.002 5.002 0 1 1 0 -10.004 5.002 5.002 0 0 1 0 10.004"
+ android:fillColor="#99FFFFFF"/>
</vector> \ No newline at end of file
diff --git a/core/res/res/drawable/pointer_spot_touch_vector.xml b/core/res/res/drawable/pointer_spot_touch_vector.xml
index afd2956858fa..a25ffe0da5bd 100644
--- a/core/res/res/drawable/pointer_spot_touch_vector.xml
+++ b/core/res/res/drawable/pointer_spot_touch_vector.xml
@@ -21,4 +21,7 @@
<path
android:fillColor="#ADC6E7"
android:pathData="M21 12c0-4.963-4.038-9-9-9s-9 4.037-9 9 4.038 9 9 9 9-4.037 9-9m-9 8c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8" />
+ <path
+ android:pathData="M12 12m-8 0a8 8 0 1 1 16 0a8 8 0 1 1 -16 0"
+ android:fillColor="#99FFFFFF"/>
</vector> \ No newline at end of file
diff --git a/core/res/res/values-mcc404/config.xml b/core/res/res/values-mcc404/config.xml
index 4cadef7893d3..0cb1029626b1 100644
--- a/core/res/res/values-mcc404/config.xml
+++ b/core/res/res/values-mcc404/config.xml
@@ -18,8 +18,6 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Whether camera shutter sound is forced or not (country specific). -->
- <bool name="config_camera_sound_forced">true</bool>
<!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
<bool name="config_showAreaUpdateInfoSettings">true</bool>
</resources>
diff --git a/core/res/res/values-mcc405/config.xml b/core/res/res/values-mcc405/config.xml
index 4cadef7893d3..0cb1029626b1 100644
--- a/core/res/res/values-mcc405/config.xml
+++ b/core/res/res/values-mcc405/config.xml
@@ -18,8 +18,6 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Whether camera shutter sound is forced or not (country specific). -->
- <bool name="config_camera_sound_forced">true</bool>
<!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
<bool name="config_showAreaUpdateInfoSettings">true</bool>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d4db244a852b..2115f64a60b3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4334,9 +4334,6 @@
-->
<bool name="config_wallpaperTopApp">false</bool>
- <!-- True if the device supports dVRR -->
- <bool name="config_supportsDvrr">false</bool>
-
<!-- True if the device supports at least one form of multi-window.
E.g. freeform, split-screen, picture-in-picture. -->
<bool name="config_supportsMultiWindow">true</bool>
diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml
index e42962ce9195..ae4789937832 100644
--- a/core/res/res/values/config_battery_stats.xml
+++ b/core/res/res/values/config_battery_stats.xml
@@ -32,6 +32,9 @@
devices-->
<integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer>
+ <!-- Mobile Radio power stats collection throttle period in milliseconds. -->
+ <integer name="config_defaultPowerStatsThrottlePeriodMobileRadio">3600000</integer>
+
<!-- PowerStats aggregation period in milliseconds. This is the interval at which the power
stats aggregation procedure is performed and the results stored in PowerStatsStore. -->
<integer name="config_powerStatsAggregationPeriod">14400000</integer>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f33e2771879d..9e0954093eb2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -404,7 +404,6 @@
<java-symbol type="bool" name="config_supportAudioSourceUnprocessed" />
<java-symbol type="bool" name="config_freeformWindowManagement" />
<java-symbol type="bool" name="config_supportsBubble" />
- <java-symbol type="bool" name="config_supportsDvrr" />
<java-symbol type="bool" name="config_supportsMultiWindow" />
<java-symbol type="bool" name="config_supportsSplitScreenMultiWindow" />
<java-symbol type="bool" name="config_supportsMultiDisplay" />
@@ -5217,6 +5216,7 @@
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
<java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" />
+ <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodMobileRadio" />
<java-symbol type="integer" name="config_powerStatsAggregationPeriod" />
<java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" />
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index c8625b9114da..9bb72d9db6c7 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -28,7 +28,7 @@
standard SMS rate. The user is warned when the destination phone number matches the
"pattern" or "premium" regexes, and does not match the "free" or "standard" regexes. -->
- <!-- Harmonised European Short Codes are 6 digit numbers starting with 116 (free helplines).
+ <!-- Harmonised European Short Codes are 7 digit numbers starting with 116 (free helplines).
Premium patterns include short codes from: http://aonebill.com/coverage&tariffs
and http://mobilcent.com/info-worldwide.asp and extracted from:
http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ -->
@@ -39,8 +39,8 @@
<!-- Albania: 5 digits, known short codes listed -->
<shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" />
- <!-- Argentina: 5 digits, known short codes listed -->
- <shortcode country="ar" pattern="\\d{5}" free="11711|28291|44077|78887" />
+ <!-- Argentina: 6 digits, known short codes listed -->
+ <shortcode country="ar" pattern="\\d{1,6}" free="11711|28291|44077|78887|191289|39010" />
<!-- Armenia: 3-5 digits, emergency numbers 10[123] -->
<shortcode country="am" pattern="\\d{3,5}" premium="11[2456]1|3024" free="10[123]|71522|71512|71502" />
@@ -67,7 +67,7 @@
<shortcode country="bh" pattern="\\d{1,5}" free="81181|85999" />
<!-- Brazil: 1-5 digits (standard system default, not country specific) -->
- <shortcode country="br" pattern="\\d{1,5}" free="6000[012]\\d|876|5500|9963|4141|8000" />
+ <shortcode country="br" pattern="\\d{1,5}" free="6000[012]\\d|876|5500|9963|4141|8000|2652" />
<!-- Belarus: 4 digits -->
<shortcode country="by" pattern="\\d{4}" premium="3336|4161|444[4689]|501[34]|7781" />
@@ -163,7 +163,7 @@
<shortcode country="in" pattern="\\d{1,5}" free="59336|53969" />
<!-- Indonesia: 1-5 digits (standard system default, not country specific) -->
- <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363|93457" />
+ <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363|93457|99265" />
<!-- Ireland: 5 digits, 5xxxx (50xxx=free, 5[12]xxx=standard), plus EU:
http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf -->
@@ -172,6 +172,9 @@
<!-- Israel: 1-5 digits, known premium codes listed -->
<shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477|6681" />
+ <!-- Iran: 4-6 digits, known premium codes listed -->
+ <shortcode country="ir" pattern="\\d{4,6}" free="700791|700792" />
+
<!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU:
https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf -->
<shortcode country="it" pattern="\\d{5}" premium="44[0-4]\\d{2}|47[0-4]\\d{2}|48[0-4]\\d{2}|44[5-9]\\d{4}|47[5-9]\\d{4}|48[5-9]\\d{4}|455\\d{2}|499\\d{2}" free="116\\d{3}|4112503|40\\d{0,12}" standard="430\\d{2}|431\\d{2}|434\\d{4}|435\\d{4}|439\\d{7}" />
@@ -219,11 +222,11 @@
<!-- Mozambique: 1-5 digits (standard system default, not country specific) -->
<shortcode country="mz" pattern="\\d{1,5}" free="1714" />
- <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
- <shortcode country="mx" pattern="\\d{4,6}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453|550346" />
+ <!-- Mexico: 4-7 digits (not confirmed), known premium codes listed -->
+ <shortcode country="mx" pattern="\\d{4,7}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453|550346|3030303" />
<!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
- <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" />
+ <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668|66966" />
<!-- Namibia: 1-5 digits (standard system default, not country specific) -->
<shortcode country="na" pattern="\\d{1,5}" free="40005" />
@@ -255,6 +258,9 @@
<!-- Palestine: 5 digits, known premium codes listed -->
<shortcode country="ps" pattern="\\d{1,5}" free="37477|6681" />
+ <!-- Paraguay: 6 digits, known premium codes listed -->
+ <shortcode country="py" pattern="\\d{6}" free="191289" />
+
<!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU -->
<shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012|80921" />
@@ -275,7 +281,7 @@
<shortcode country="ru" pattern="\\d{4}" premium="1(?:1[56]1|899)|2(?:09[57]|322|47[46]|880|990)|3[589]33|4161|44(?:4[3-9]|81)|77(?:33|81)|8424" free="6954|8501" standard="2037|2044"/>
<!-- Rwanda: 4 digits -->
- <shortcode country="rw" pattern="\\d{4}" free="5060" />
+ <shortcode country="rw" pattern="\\d{4}" free="5060|5061" />
<!-- Saudi Arabia -->
<shortcode country="sa" pattern="\\d{1,5}" free="8145" />
@@ -309,7 +315,10 @@
<shortcode country="tj" pattern="\\d{4}" premium="11[3-7]1|4161|4333|444[689]" />
<!-- Tanzania: 1-5 digits (standard system default, not country specific) -->
- <shortcode country="tz" pattern="\\d{1,5}" free="15046|15234" />
+ <shortcode country="tz" pattern="\\d{1,5}" free="15046|15234|15324" />
+
+ <!-- Tunisia: 5 digits, known premium codes listed -->
+ <shortcode country="tn" pattern="\\d{5}" free="85799" />
<!-- Turkey -->
<shortcode country="tr" pattern="\\d{1,5}" free="7529|5528|6493|3193" />
@@ -324,8 +333,11 @@
visual voicemail code for T-Mobile: 122 -->
<shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245|611611|96831" />
- <!--Uruguay : 1-5 digits (standard system default, not country specific) -->
- <shortcode country="uy" pattern="\\d{1,5}" free="55002" />
+ <!--Uruguay : 1-6 digits (standard system default, not country specific) -->
+ <shortcode country="uy" pattern="\\d{1,6}" free="55002|191289" />
+
+ <!-- Venezuela: 1-6 digits (standard system default, not country specific) -->
+ <shortcode country="ve" pattern="\\d{1,6}" free="538352" />
<!-- Vietnam: 1-5 digits (standard system default, not country specific) -->
<shortcode country="vn" pattern="\\d{1,5}" free="5001|9055|8079" />
@@ -336,6 +348,9 @@
<!-- South Africa -->
<shortcode country="za" pattern="\\d{1,5}" free="44136|30791|36056|33009" />
+ <!-- Yemen -->
+ <shortcode country="ye" pattern="\\d{1,4}" free="5081" />
+
<!-- Zimbabwe -->
<shortcode country="zw" pattern="\\d{1,5}" free="33679" />
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index 6cc54850dce6..8072d6925675 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -16,6 +16,8 @@
package com.android.os.bugreports.tests;
+import static android.content.Context.RECEIVER_EXPORTED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNotNull;
@@ -358,7 +360,10 @@ public class BugreportManagerTest {
// shell UID rather than our own.
BugreportBroadcastReceiver br = new BugreportBroadcastReceiver();
InstrumentationRegistry.getContext()
- .registerReceiver(br, new IntentFilter(INTENT_BUGREPORT_FINISHED));
+ .registerReceiver(
+ br,
+ new IntentFilter(INTENT_BUGREPORT_FINISHED),
+ RECEIVER_EXPORTED);
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
.executeShellCommand("am bug-report");
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 48082048691c..404e8731d5c5 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -267,5 +267,10 @@ android_ravenwood_test {
generate_get_transaction_name: true,
local_include_dirs: ["aidl"],
},
+ java_resources: [
+ "res/xml/power_profile_test.xml",
+ "res/xml/power_profile_test_cpu_legacy.xml",
+ "res/xml/power_profile_test_modem.xml",
+ ],
auto_gen_config: true,
}
diff --git a/core/tests/coretests/res/xml/power_profile_test.xml b/core/tests/coretests/res/xml/power_profile_test.xml
index 322ae05bc63e..7356c9e38012 100644
--- a/core/tests/coretests/res/xml/power_profile_test.xml
+++ b/core/tests/coretests/res/xml/power_profile_test.xml
@@ -98,4 +98,16 @@
<value>40</value>
<value>50</value>
</array>
-</device> \ No newline at end of file
+
+ <!-- Idle current for bluetooth in mA.-->
+ <item name="bluetooth.controller.idle">0.02</item>
+
+ <!-- Rx current for bluetooth in mA.-->
+ <item name="bluetooth.controller.rx">3</item>
+
+ <!-- Tx current for bluetooth in mA-->
+ <item name="bluetooth.controller.tx">5</item>
+
+ <!-- Operating voltage for bluetooth in mV.-->
+ <item name="bluetooth.controller.voltage">3300</item>
+</device>
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index 2ce7a7d3d70d..a0aff6e7b9a0 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -16,7 +16,6 @@
package android.app.servertransaction;
-import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.view.Display.DEFAULT_DISPLAY;
import static org.junit.Assert.assertEquals;
@@ -29,9 +28,6 @@ import static org.mockito.Mockito.verify;
import android.app.Activity;
import android.app.ActivityThread;
import android.app.ClientTransactionHandler;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
import android.os.RemoteException;
@@ -107,35 +103,6 @@ public class ClientTransactionItemTest {
}
@Test
- public void testActivityConfigurationChangeItem_getContextToUpdate() {
- final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem
- .obtain(mActivityToken, mConfiguration, mActivityWindowInfo);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(mActivity, context);
- }
-
- @Test
- public void testActivityRelaunchItem_getContextToUpdate() {
- final ActivityRelaunchItem item = ActivityRelaunchItem
- .obtain(mActivityToken, null /* pendingResults */, null /* pendingNewIntents */,
- 0 /* configChange */, mMergedConfiguration, false /* preserveWindow */,
- mActivityWindowInfo);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(mActivity, context);
- }
-
- @Test
- public void testConfigurationChangeItem_getContextToUpdate() {
- final ConfigurationChangeItem item = ConfigurationChangeItem
- .obtain(mConfiguration, DEVICE_ID_DEFAULT);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(ActivityThread.currentApplication(), context);
- }
-
- @Test
public void testDestroyActivityItem_preExecute() {
final DestroyActivityItem item = DestroyActivityItem
.obtain(mActivityToken, false /* finished */);
@@ -166,26 +133,6 @@ public class ClientTransactionItemTest {
}
@Test
- public void testLaunchActivityItem_getContextToUpdate() {
- final LaunchActivityItem item = new TestUtils.LaunchActivityItemBuilder(
- mActivityToken, new Intent(), new ActivityInfo())
- .build();
-
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(ActivityThread.currentApplication(), context);
- }
-
- @Test
- public void testMoveToDisplayItem_getContextToUpdate() {
- final MoveToDisplayItem item = MoveToDisplayItem
- .obtain(mActivityToken, DEFAULT_DISPLAY, mConfiguration, mActivityWindowInfo);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(mActivity, context);
- }
-
- @Test
public void testWindowContextInfoChangeItem_execute() {
final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
.obtain(mWindowClientToken, mConfiguration, DEFAULT_DISPLAY);
@@ -196,17 +143,6 @@ public class ClientTransactionItemTest {
}
@Test
- public void testWindowContextInfoChangeItem_getContextToUpdate() {
- doReturn(mWindowContext).when(mHandler).getWindowContext(mWindowClientToken);
-
- final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
- .obtain(mWindowClientToken, mConfiguration, DEFAULT_DISPLAY);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(mWindowContext, context);
- }
-
- @Test
public void testWindowContextWindowRemovalItem_execute() {
final WindowContextWindowRemovalItem item = WindowContextWindowRemovalItem.obtain(
mWindowClientToken);
@@ -220,7 +156,7 @@ public class ClientTransactionItemTest {
final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames,
true /* reportDraw */, mMergedConfiguration, mInsetsState, true /* forceLayout */,
true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
- true /* dragResizing */, mActivityToken, mActivityWindowInfo);
+ true /* dragResizing */, mActivityWindowInfo);
item.execute(mHandler, mPendingActions);
verify(mWindow).resized(mFrames,
@@ -228,16 +164,4 @@ public class ClientTransactionItemTest {
true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
true /* dragResizing */, mActivityWindowInfo);
}
-
- @Test
- public void testWindowStateResizeItem_getContextToUpdate() {
- final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames,
- true /* reportDraw */, mMergedConfiguration, mInsetsState, true /* forceLayout */,
- true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
- true /* dragResizing */, mActivityToken, mActivityWindowInfo);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(ActivityThread.currentApplication(), context);
- }
-
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 77d31a5f27e7..8506905e6ca0 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -23,11 +23,17 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
@@ -76,6 +82,13 @@ public class ClientTransactionListenerControllerTest {
private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
@Mock
private IBinder mActivityToken;
+ @Mock
+ private Activity mActivity;
+ @Mock
+ private Resources mResources;
+
+ private Configuration mConfiguration;
+
private DisplayManagerGlobal mDisplayManager;
private Handler mHandler;
@@ -88,7 +101,12 @@ public class ClientTransactionListenerControllerTest {
MockitoAnnotations.initMocks(this);
mDisplayManager = new DisplayManagerGlobal(mIDisplayManager);
mHandler = getInstrumentation().getContext().getMainThreadHandler();
- mController = ClientTransactionListenerController.createInstanceForTesting(mDisplayManager);
+ mController = spy(ClientTransactionListenerController
+ .createInstanceForTesting(mDisplayManager));
+
+ mConfiguration = new Configuration();
+ doReturn(mConfiguration).when(mResources).getConfiguration();
+ doReturn(mResources).when(mActivity).getResources();
}
@Test
@@ -107,6 +125,43 @@ public class ClientTransactionListenerControllerTest {
}
@Test
+ public void testOnContextConfigurationChanged() {
+ doNothing().when(mController).onDisplayChanged(anyInt());
+ doReturn(123).when(mActivity).getDisplayId();
+
+ // Not trigger onDisplayChanged when there is no change.
+ mController.onContextConfigurationPreChanged(mActivity);
+ mController.onContextConfigurationPostChanged(mActivity);
+
+ verify(mController, never()).onDisplayChanged(anyInt());
+
+ mController.onContextConfigurationPreChanged(mActivity);
+ mConfiguration.windowConfiguration.setMaxBounds(new Rect(0, 0, 100, 200));
+ mController.onContextConfigurationPostChanged(mActivity);
+
+ verify(mController).onDisplayChanged(123);
+ }
+
+ @Test
+ public void testOnContextConfigurationChanged_duringClientTransaction() {
+ doNothing().when(mController).onDisplayChanged(anyInt());
+ doReturn(123).when(mActivity).getDisplayId();
+
+ // Not trigger onDisplayChanged until ClientTransaction finished execution.
+ mController.onClientTransactionStarted();
+
+ mController.onContextConfigurationPreChanged(mActivity);
+ mConfiguration.windowConfiguration.setMaxBounds(new Rect(0, 0, 100, 200));
+ mController.onContextConfigurationPostChanged(mActivity);
+
+ verify(mController, never()).onDisplayChanged(anyInt());
+
+ mController.onClientTransactionFinished();
+
+ verify(mController).onDisplayChanged(123);
+ }
+
+ @Test
public void testActivityWindowInfoChangedListener() {
mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
diff --git a/core/tests/coretests/src/android/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java
index 6a6e65288b7a..bad048523053 100644
--- a/core/tests/coretests/src/android/view/MotionEventTest.java
+++ b/core/tests/coretests/src/android/view/MotionEventTest.java
@@ -26,6 +26,7 @@ import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
import android.graphics.Matrix;
import android.platform.test.annotations.Presubmit;
@@ -232,4 +233,66 @@ public class MotionEventTest {
assertEquals(10, (int) event.getX());
assertEquals(20, (int) event.getY());
}
+
+ @Test
+ public void testSplit() {
+ final int pointerCount = 2;
+ final var properties = new PointerProperties[]{fingerProperties(0), fingerProperties(1)};
+ final var coords = new PointerCoords[]{pointerCoords(20, 60), pointerCoords(40, 40)};
+
+ final MotionEvent event = MotionEvent.obtain(0 /* downTime */,
+ 0 /* eventTime */, MotionEvent.ACTION_MOVE, pointerCount, properties, coords,
+ 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, 1 /* yPrecision */,
+ 0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN,
+ 0 /* flags */);
+
+ final int idBits = ~0b1 & event.getPointerIdBits();
+ final MotionEvent splitEvent = event.split(idBits);
+ assertEquals(1, splitEvent.getPointerCount());
+ assertEquals(1, splitEvent.getPointerId(0));
+ assertEquals(40, (int) splitEvent.getX());
+ assertEquals(40, (int) splitEvent.getY());
+ }
+
+ @Test
+ public void testSplitFailsWhenNoIdsSpecified() {
+ final int pointerCount = 2;
+ final var properties = new PointerProperties[]{fingerProperties(0), fingerProperties(1)};
+ final var coords = new PointerCoords[]{pointerCoords(20, 60), pointerCoords(40, 40)};
+
+ final MotionEvent event = MotionEvent.obtain(0 /* downTime */,
+ 0 /* eventTime */, MotionEvent.ACTION_MOVE, pointerCount, properties, coords,
+ 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, 1 /* yPrecision */,
+ 0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN,
+ 0 /* flags */);
+
+ try {
+ final MotionEvent splitEvent = event.split(0);
+ fail("Splitting event with id bits 0 should throw: " + splitEvent);
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void testSplitFailsWhenIdBitsDoNotMatch() {
+ final int pointerCount = 2;
+ final var properties = new PointerProperties[]{fingerProperties(0), fingerProperties(1)};
+ final var coords = new PointerCoords[]{pointerCoords(20, 60), pointerCoords(40, 40)};
+
+ final MotionEvent event = MotionEvent.obtain(0 /* downTime */,
+ 0 /* eventTime */, MotionEvent.ACTION_MOVE, pointerCount, properties, coords,
+ 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, 1 /* yPrecision */,
+ 0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN,
+ 0 /* flags */);
+
+ try {
+ final int idBits = 0b100;
+ final MotionEvent splitEvent = event.split(idBits);
+ fail("Splitting event with id bits that do not match any pointers should throw: "
+ + splitEvent);
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 365f3485d65c..d560ef243a06 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -64,6 +64,9 @@ import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.speech.tts.Voice;
@@ -71,6 +74,7 @@ import android.test.mock.MockContentResolver;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityManager;
import android.widget.Toast;
@@ -83,9 +87,11 @@ import com.android.internal.util.test.FakeSettingsProvider;
import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -93,11 +99,14 @@ import org.mockito.invocation.InvocationOnMock;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class AccessibilityShortcutControllerTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name";
private static final CharSequence PACKAGE_NAME_STRING = "Service name";
private static final String SERVICE_NAME_SUMMARY = "Summary";
@@ -126,6 +135,7 @@ public class AccessibilityShortcutControllerTest {
private @Mock TextToSpeech mTextToSpeech;
private @Mock Voice mVoice;
private @Mock Ringtone mRingtone;
+ private @Captor ArgumentCaptor<List<String>> mListCaptor;
private MockContentResolver mContentResolver;
private WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
@@ -410,6 +420,7 @@ public class AccessibilityShortcutControllerTest {
}
@Test
+ @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
public void testClickingDisableButtonInDialog_shouldClearShortcutId() throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
configureValidShortcutService();
@@ -423,6 +434,29 @@ public class AccessibilityShortcutControllerTest {
captor.capture());
captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(false), eq(HARDWARE), mListCaptor.capture(), anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(SERVICE_NAME_STRING);
+ assertThat(Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
+ public void testClickingDisableButtonInDialog_shouldClearShortcutId_old() throws Exception {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ getController().performAccessibilityShortcut();
+
+ ArgumentCaptor<DialogInterface.OnClickListener> captor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
+ captor.capture());
+ captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+
assertThat(
Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)
).isEmpty();
@@ -432,6 +466,8 @@ public class AccessibilityShortcutControllerTest {
}
@Test
+ @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
+ @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
public void turnOffVolumeShortcutForAlwaysOnA11yService_shouldTurnOffA11yService()
throws Exception {
configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
@@ -443,6 +479,8 @@ public class AccessibilityShortcutControllerTest {
}
@Test
+ @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
+ @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
public void turnOffVolumeShortcutForAlwaysOnA11yService_hasOtherTypesShortcut_shouldNotTurnOffA11yService()
throws Exception {
configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
@@ -489,6 +527,7 @@ public class AccessibilityShortcutControllerTest {
}
@Test
+ @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn()
throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
@@ -503,15 +542,39 @@ public class AccessibilityShortcutControllerTest {
captor.capture());
captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE);
- assertThat(
- Settings.Secure.getString(mContentResolver,
- ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(true), eq(HARDWARE), mListCaptor.capture(), anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(SERVICE_NAME_STRING);
+ assertThat(Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+ AccessibilityShortcutController.DialogStatus.SHOWN);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
+ public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn_old()
+ throws Exception {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureDefaultAccessibilityService();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ getController().performAccessibilityShortcut();
+
+ ArgumentCaptor<DialogInterface.OnClickListener> captor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.accessibility_shortcut_on),
+ captor.capture());
+ captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE);
+
+ assertThat(Settings.Secure.getString(mContentResolver,
+ ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
assertThat(Settings.Secure.getInt(
mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
AccessibilityShortcutController.DialogStatus.SHOWN);
}
@Test
+ @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff()
throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
@@ -526,9 +589,32 @@ public class AccessibilityShortcutControllerTest {
captor.capture());
captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
- assertThat(
- Settings.Secure.getString(mContentResolver,
- ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEmpty();
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(false), eq(HARDWARE), mListCaptor.capture(), anyInt());
+ assertThat(mListCaptor.getValue()).isEmpty();
+ assertThat(Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
+ public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff_old()
+ throws Exception {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureDefaultAccessibilityService();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ getController().performAccessibilityShortcut();
+
+ ArgumentCaptor<DialogInterface.OnClickListener> captor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
+ captor.capture());
+ captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+ assertThat(Settings.Secure.getString(mContentResolver,
+ ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEmpty();
assertThat(Settings.Secure.getInt(
mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
index 2ea044ccfb52..9cac3120d1d9 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
@@ -19,8 +19,10 @@ package com.android.internal.accessibility.dialog;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -31,8 +33,12 @@ import android.content.pm.ParceledListSlice;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityManager;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -47,10 +53,13 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Collections;
+import java.util.List;
/**
* Unit Tests for
@@ -59,9 +68,13 @@ import java.util.Collections;
@RunWith(AndroidJUnit4.class)
public class InvisibleToggleAccessibilityServiceTargetTest {
@Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Mock
private IAccessibilityManager mAccessibilityManagerService;
+ @Captor
+ private ArgumentCaptor<List<String>> mListCaptor;
private static final String ALWAYS_ON_SERVICE_PACKAGE_LABEL = "always on a11y service";
private static final String ALWAYS_ON_SERVICE_COMPONENT_NAME =
@@ -104,6 +117,32 @@ public class InvisibleToggleAccessibilityServiceTargetTest {
}
@Test
+ @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
+ public void onCheckedChanged_true_callA11yManagerToUpdateShortcuts() throws Exception {
+ mSut.onCheckedChanged(true);
+
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(true),
+ eq(ShortcutConstants.UserShortcutType.HARDWARE),
+ mListCaptor.capture(),
+ anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(ALWAYS_ON_SERVICE_COMPONENT_NAME);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
+ public void onCheckedChanged_false_callA11yManagerToUpdateShortcuts() throws Exception {
+ mSut.onCheckedChanged(false);
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(false),
+ eq(ShortcutConstants.UserShortcutType.HARDWARE),
+ mListCaptor.capture(),
+ anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(ALWAYS_ON_SERVICE_COMPONENT_NAME);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
public void onCheckedChanged_turnOnShortcut_hasOtherShortcut_serviceKeepsOn() {
enableA11yService(/* enable= */ true);
addShortcutForA11yService(
@@ -116,6 +155,7 @@ public class InvisibleToggleAccessibilityServiceTargetTest {
}
@Test
+ @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
public void onCheckedChanged_turnOnShortcut_noOtherShortcut_shouldTurnOnService() {
enableA11yService(/* enable= */ false);
addShortcutForA11yService(
@@ -128,6 +168,7 @@ public class InvisibleToggleAccessibilityServiceTargetTest {
}
@Test
+ @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
public void onCheckedChanged_turnOffShortcut_hasOtherShortcut_serviceKeepsOn() {
enableA11yService(/* enable= */ true);
addShortcutForA11yService(
@@ -140,6 +181,7 @@ public class InvisibleToggleAccessibilityServiceTargetTest {
}
@Test
+ @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
public void onCheckedChanged_turnOffShortcut_noOtherShortcut_shouldTurnOffService() {
enableA11yService(/* enable= */ true);
addShortcutForA11yService(
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index 533b799341c1..fa5d72a04b88 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -55,6 +55,21 @@ public class LongArrayMultiStateCounterTest {
}
@Test
+ public void copyStatesFrom() {
+ LongArrayMultiStateCounter source = new LongArrayMultiStateCounter(2, 1);
+ updateValue(source, new long[]{0}, 1000);
+ source.setState(0, 1000);
+ source.setState(1, 2000);
+
+ LongArrayMultiStateCounter target = new LongArrayMultiStateCounter(2, 1);
+ target.copyStatesFrom(source);
+ updateValue(target, new long[]{1000}, 5000);
+
+ assertCounts(target, 0, new long[]{250});
+ assertCounts(target, 1, new long[]{750});
+ }
+
+ @Test
public void setValue() {
LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
index c0f0714e52cc..951fa98caf27 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
@@ -21,20 +21,21 @@ import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT;
import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
-import android.annotation.XmlRes;
import android.content.Context;
import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Xml;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.frameworks.coretests.R;
import com.android.internal.power.ModemPowerProfile;
import com.android.internal.util.XmlUtils;
@@ -43,6 +44,11 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.StringReader;
/*
* Keep this file in sync with frameworks/base/core/res/res/xml/power_profile_test.xml and
@@ -53,7 +59,6 @@ import org.junit.runner.RunWith;
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = PowerProfile.class)
public class PowerProfileTest {
@Rule
public final RavenwoodRule mRavenwood = new RavenwoodRule();
@@ -62,17 +67,15 @@ public class PowerProfileTest {
static final String ATTR_NAME = "name";
private PowerProfile mProfile;
- private Context mContext;
@Before
public void setUp() {
- mContext = InstrumentationRegistry.getContext();
- mProfile = new PowerProfile(mContext);
+ mProfile = new PowerProfile();
}
@Test
public void testPowerProfile() {
- mProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+ mProfile.initForTesting(resolveParser("power_profile_test"));
assertEquals(5.0, mProfile.getAveragePower(PowerProfile.POWER_CPU_SUSPEND));
assertEquals(1.11, mProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE));
@@ -127,11 +130,36 @@ public class PowerProfileTest {
PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT
| ModemPowerProfile.MODEM_DRAIN_TYPE_TX
| ModemPowerProfile.MODEM_TX_LEVEL_4));
+
+ assertEquals(0.02, mProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE));
+ assertEquals(3, mProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX));
+ assertEquals(5, mProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX));
+ assertEquals(3300, mProfile.getAveragePower(
+ PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE));
+ }
+
+ @DisabledOnRavenwood
+ @Test
+ public void configDefaults() throws XmlPullParserException {
+ Resources mockResources = mock(Resources.class);
+ when(mockResources.getInteger(com.android.internal.R.integer.config_bluetooth_rx_cur_ma))
+ .thenReturn(123);
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new StringReader(
+ "<device name='Android'>"
+ + "<item name='bluetooth.controller.idle'>10</item>"
+ + "</device>"));
+ mProfile.initForTesting(parser, mockResources);
+ assertThat(mProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE))
+ .isEqualTo(10);
+ assertThat(mProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX))
+ .isEqualTo(123);
}
+
@Test
public void testPowerProfile_legacyCpuConfig() {
// This power profile has per-cluster data, rather than per-policy
- mProfile.forceInitForTesting(mContext, R.xml.power_profile_test_cpu_legacy);
+ mProfile.initForTesting(resolveParser("power_profile_test_cpu_legacy"));
assertEquals(2.11, mProfile.getAveragePowerForCpuScalingPolicy(0));
assertEquals(2.22, mProfile.getAveragePowerForCpuScalingPolicy(4));
@@ -148,7 +176,7 @@ public class PowerProfileTest {
@Test
public void testModemPowerProfile_defaultRat() throws Exception {
- final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem,
+ final XmlPullParser parser = getTestModemElement("power_profile_test_modem",
"testModemPowerProfile_defaultRat");
ModemPowerProfile mpp = new ModemPowerProfile();
mpp.parseFromXml(parser);
@@ -216,7 +244,7 @@ public class PowerProfileTest {
@Test
public void testModemPowerProfile_partiallyDefined() throws Exception {
- final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem,
+ final XmlPullParser parser = getTestModemElement("power_profile_test_modem",
"testModemPowerProfile_partiallyDefined");
ModemPowerProfile mpp = new ModemPowerProfile();
mpp.parseFromXml(parser);
@@ -369,7 +397,7 @@ public class PowerProfileTest {
@Test
public void testModemPowerProfile_fullyDefined() throws Exception {
- final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem,
+ final XmlPullParser parser = getTestModemElement("power_profile_test_modem",
"testModemPowerProfile_fullyDefined");
ModemPowerProfile mpp = new ModemPowerProfile();
mpp.parseFromXml(parser);
@@ -519,11 +547,10 @@ public class PowerProfileTest {
| ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4));
}
- private XmlResourceParser getTestModemElement(@XmlRes int xmlId, String elementName)
+ private XmlPullParser getTestModemElement(String resourceName, String elementName)
throws Exception {
+ XmlPullParser parser = resolveParser(resourceName);
final String element = TAG_TEST_MODEM;
- final Resources resources = mContext.getResources();
- XmlResourceParser parser = resources.getXml(xmlId);
while (true) {
XmlUtils.nextElement(parser);
final String e = parser.getName();
@@ -535,10 +562,26 @@ public class PowerProfileTest {
return parser;
}
- fail("Unanable to find element " + element + " with name " + elementName);
+ fail("Unable to find element " + element + " with name " + elementName);
return null;
}
+ private XmlPullParser resolveParser(String resourceName) {
+ if (RavenwoodRule.isOnRavenwood()) {
+ try {
+ return Xml.resolvePullParser(getClass().getClassLoader()
+ .getResourceAsStream("res/xml/" + resourceName + ".xml"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ Context context = androidx.test.InstrumentationRegistry.getContext();
+ Resources resources = context.getResources();
+ int resId = resources.getIdentifier(resourceName, "xml", context.getPackageName());
+ return resources.getXml(resId);
+ }
+ }
+
private void assertEquals(double expected, double actual) {
Assert.assertEquals(expected, actual, 0.1);
}
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
index b99e2026ef26..6402206410b5 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -21,20 +21,27 @@ import static com.google.common.truth.Truth.assertThat;
import android.os.BatteryConsumer;
import android.os.Parcel;
import android.os.PersistableBundle;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.SparseArray;
+import android.util.Xml;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
-@IgnoreUnderRavenwood(reason = "Needs kernel support")
public class PowerStatsTest {
@Rule
public final RavenwoodRule mRavenwood = new RavenwoodRule();
@@ -47,7 +54,10 @@ public class PowerStatsTest {
mRegistry = new PowerStats.DescriptorRegistry();
PersistableBundle extras = new PersistableBundle();
extras.putBoolean("hasPowerMonitor", true);
- mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 3, 2, extras);
+ SparseArray<String> stateLabels = new SparseArray<>();
+ stateLabels.put(0x0F, "idle");
+ mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 3, stateLabels,
+ 1, 2, extras);
mRegistry.register(mDescriptor);
}
@@ -58,6 +68,8 @@ public class PowerStatsTest {
stats.stats[0] = 10;
stats.stats[1] = 20;
stats.stats[2] = 30;
+ stats.stateStats.put(0x0F, new long[]{16});
+ stats.stateStats.put(0xF0, new long[]{17});
stats.uidStats.put(42, new long[]{40, 50});
stats.uidStats.put(99, new long[]{60, 70});
@@ -73,6 +85,7 @@ public class PowerStatsTest {
assertThat(newDescriptor.powerComponentId).isEqualTo(BatteryConsumer.POWER_COMPONENT_CPU);
assertThat(newDescriptor.name).isEqualTo("cpu");
assertThat(newDescriptor.statsArrayLength).isEqualTo(3);
+ assertThat(newDescriptor.stateStatsArrayLength).isEqualTo(1);
assertThat(newDescriptor.uidStatsArrayLength).isEqualTo(2);
assertThat(newDescriptor.extras.getBoolean("hasPowerMonitor")).isTrue();
@@ -81,6 +94,11 @@ public class PowerStatsTest {
PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
assertThat(newStats.durationMs).isEqualTo(1234);
assertThat(newStats.stats).isEqualTo(new long[]{10, 20, 30});
+ assertThat(newStats.stateStats.size()).isEqualTo(2);
+ assertThat(newStats.stateStats.get(0x0F)).isEqualTo(new long[]{16});
+ assertThat(newStats.descriptor.getStateLabel(0x0F)).isEqualTo("idle");
+ assertThat(newStats.stateStats.get(0xF0)).isEqualTo(new long[]{17});
+ assertThat(newStats.descriptor.getStateLabel(0xF0)).isEqualTo("cpu-f0");
assertThat(newStats.uidStats.size()).isEqualTo(2);
assertThat(newStats.uidStats.get(42)).isEqualTo(new long[]{40, 50});
assertThat(newStats.uidStats.get(99)).isEqualTo(new long[]{60, 70});
@@ -90,9 +108,33 @@ public class PowerStatsTest {
}
@Test
+ public void xmlFormat() throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ TypedXmlSerializer serializer = Xml.newBinarySerializer();
+ serializer.setOutput(out, StandardCharsets.UTF_8.name());
+ mDescriptor.writeXml(serializer);
+ serializer.flush();
+
+ byte[] bytes = out.toByteArray();
+
+ TypedXmlPullParser parser = Xml.newBinaryPullParser();
+ parser.setInput(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8.name());
+ PowerStats.Descriptor actual = PowerStats.Descriptor.createFromXml(parser);
+
+ assertThat(actual.powerComponentId).isEqualTo(BatteryConsumer.POWER_COMPONENT_CPU);
+ assertThat(actual.name).isEqualTo("cpu");
+ assertThat(actual.statsArrayLength).isEqualTo(3);
+ assertThat(actual.stateStatsArrayLength).isEqualTo(1);
+ assertThat(actual.getStateLabel(0x0F)).isEqualTo("idle");
+ assertThat(actual.getStateLabel(0xF0)).isEqualTo("cpu-f0");
+ assertThat(actual.uidStatsArrayLength).isEqualTo(2);
+ assertThat(actual.extras.getBoolean("hasPowerMonitor")).isEqualTo(true);
+ }
+
+ @Test
public void parceling_unrecognizedPowerComponent() {
PowerStats stats = new PowerStats(
- new PowerStats.Descriptor(777, "luck", 3, 2, new PersistableBundle()));
+ new PowerStats.Descriptor(777, "luck", 3, null, 1, 2, new PersistableBundle()));
stats.durationMs = 1234;
Parcel parcel = Parcel.obtain();
diff --git a/core/tests/mockingcoretests/Android.bp b/core/tests/mockingcoretests/Android.bp
index 2d778b1218d2..aca52a870655 100644
--- a/core/tests/mockingcoretests/Android.bp
+++ b/core/tests/mockingcoretests/Android.bp
@@ -40,6 +40,7 @@ android_test {
"platform-test-annotations",
"truth",
"testables",
+ "flag-junit",
],
libs: [
diff --git a/core/tests/mockingcoretests/src/android/widget/OWNERS b/core/tests/mockingcoretests/src/android/widget/OWNERS
new file mode 100644
index 000000000000..c0cbea98cc57
--- /dev/null
+++ b/core/tests/mockingcoretests/src/android/widget/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/notification/OWNERS \ No newline at end of file
diff --git a/core/tests/mockingcoretests/src/android/widget/ToastTest.java b/core/tests/mockingcoretests/src/android/widget/ToastTest.java
new file mode 100644
index 000000000000..79bc81d57727
--- /dev/null
+++ b/core/tests/mockingcoretests/src/android/widget/ToastTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.widget;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.content.Context;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.view.View;
+import android.widget.flags.Flags;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+
+/**
+ * ToastTest tests {@link Toast}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ToastTest {
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private Context mContext;
+ private MockitoSession mMockingSession;
+ private static INotificationManager.Stub sMockNMS;
+
+ @Before
+ public void setup() {
+ mContext = InstrumentationRegistry.getContext();
+ mMockingSession =
+ ExtendedMockito.mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(ServiceManager.class)
+ .startMocking();
+
+ //Toast caches the NotificationManager service as static class member
+ if (sMockNMS == null) {
+ sMockNMS = mock(INotificationManager.Stub.class);
+ }
+ doReturn(sMockNMS).when(sMockNMS).queryLocalInterface("android.app.INotificationManager");
+ doReturn(sMockNMS).when(() -> ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ reset(sMockNMS);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_TOAST_NO_WEAKREF)
+ public void enqueueFail_nullifiesNextView() throws RemoteException {
+ Looper.prepare();
+
+ // allow 1st toast and fail on the 2nd
+ when(sMockNMS.enqueueToast(anyString(), any(), any(), anyInt(), anyBoolean(),
+ anyInt())).thenReturn(true, false);
+
+ // first toast is enqueued
+ Toast t = Toast.makeText(mContext, "Toast1", Toast.LENGTH_SHORT);
+ t.setView(mock(View.class));
+ t.show();
+ Toast.TN tn = t.getTn();
+ assertThat(tn.getNextView()).isNotNull();
+
+ // second toast is not enqueued
+ t = Toast.makeText(mContext, "Toast2", Toast.LENGTH_SHORT);
+ t.setView(mock(View.class));
+ t.show();
+ tn = t.getTn();
+ assertThat(tn.getNextView()).isNull();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_TOAST_NO_WEAKREF)
+ public void enqueueFail_doesNotNullifyNextView() throws RemoteException {
+ Looper.prepare();
+
+ // allow 1st toast and fail on the 2nd
+ when(sMockNMS.enqueueToast(anyString(), any(), any(), anyInt(), anyBoolean(),
+ anyInt())).thenReturn(true, false);
+
+ // first toast is enqueued
+ Toast t = Toast.makeText(mContext, "Toast1", Toast.LENGTH_SHORT);
+ t.setView(mock(View.class));
+ t.show();
+ Toast.TN tn = t.getTn();
+ assertThat(tn.getNextView()).isNotNull();
+
+ // second toast is not enqueued
+ t = Toast.makeText(mContext, "Toast2", Toast.LENGTH_SHORT);
+ t.setView(mock(View.class));
+ t.show();
+ tn = t.getTn();
+ assertThat(tn.getNextView()).isNotNull();
+ }
+}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp b/core/tests/overlaytests/device_non_system/Android.bp
index 28f9f3341307..dd7786a4e43f 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp
+++ b/core/tests/overlaytests/device_non_system/Android.bp
@@ -1,18 +1,16 @@
-//
-// Copyright 2019, 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
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// 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 {
// See: http://go/android-license-faq
@@ -23,8 +21,19 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-runtime_resource_overlay {
- name: "NavigationBarModeGesturalOverlayExtraWideBack",
- theme: "NavigationBarModeGesturalExtraWideBack",
- product_specific: true,
+android_test {
+ name: "OverlayDeviceTestsNonSystem",
+ team: "trendy_team_android_resources",
+ srcs: ["src/**/*.java"],
+ platform_apis: true,
+ certificate: "platform",
+ static_libs: [
+ "androidx.test.rules",
+ "testng",
+ "compatibility-device-util-axt",
+ ],
+ test_suites: ["device-tests"],
+ data: [
+ ":OverlayDeviceTestsNonSystem_AppOverlay",
+ ],
}
diff --git a/core/tests/overlaytests/device_non_system/AndroidManifest.xml b/core/tests/overlaytests/device_non_system/AndroidManifest.xml
new file mode 100644
index 000000000000..a37d1689e80e
--- /dev/null
+++ b/core/tests/overlaytests/device_non_system/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.overlaytest.non_system">
+
+ <uses-sdk android:minSdkVersion="34" />
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.overlaytest.non_system"
+ android:label="Runtime resource overlay tests for non system app" />
+</manifest>
diff --git a/core/tests/overlaytests/device_non_system/AndroidTest.xml b/core/tests/overlaytests/device_non_system/AndroidTest.xml
new file mode 100644
index 000000000000..fc47e6a90880
--- /dev/null
+++ b/core/tests/overlaytests/device_non_system/AndroidTest.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+
+<configuration description="Test module config for OverlayDeviceTestsNonSystem">
+ <option name="test-tag" value="OverlayDeviceTestsNonSystem" />
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="OverlayDeviceTestsNonSystem_AppOverlay.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparer">
+ <option name="test-package-name" value="com.android.overlaytest.non_system" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="test-user-token" value="%TEST_USER%"/>
+ <option name="run-command"
+ value="cmd overlay enable --user %TEST_USER% com.android.overlaytest.non_system.app_overlay" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="OverlayDeviceTestsNonSystem.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.overlaytest.non_system" />
+ </test>
+</configuration>
diff --git a/core/tests/overlaytests/device_non_system/res/layout/layout.xml b/core/tests/overlaytests/device_non_system/res/layout/layout.xml
new file mode 100644
index 000000000000..2cb201377d52
--- /dev/null
+++ b/core/tests/overlaytests/device_non_system/res/layout/layout.xml
@@ -0,0 +1,28 @@
+<?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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/text_view_id"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/test_string" />
+</LinearLayout> \ No newline at end of file
diff --git a/core/tests/overlaytests/device_non_system/res/values/overlayable.xml b/core/tests/overlaytests/device_non_system/res/values/overlayable.xml
new file mode 100644
index 000000000000..f8017bc35d5d
--- /dev/null
+++ b/core/tests/overlaytests/device_non_system/res/values/overlayable.xml
@@ -0,0 +1,24 @@
+<?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>
+ <overlayable name="TestResources">
+ <policy type="public">
+ <item type="string" name="test_string" />
+ </policy>
+ </overlayable>
+</resources> \ No newline at end of file
diff --git a/core/tests/overlaytests/device_non_system/res/values/strings.xml b/core/tests/overlaytests/device_non_system/res/values/strings.xml
new file mode 100644
index 000000000000..ff501a0639ff
--- /dev/null
+++ b/core/tests/overlaytests/device_non_system/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+ <string name="test_string">Original</string>
+</resources> \ No newline at end of file
diff --git a/core/tests/overlaytests/device_non_system/src/com/android/overlaytest/OverlayTest.java b/core/tests/overlaytests/device_non_system/src/com/android/overlaytest/OverlayTest.java
new file mode 100644
index 000000000000..2b0fe6cdc559
--- /dev/null
+++ b/core/tests/overlaytests/device_non_system/src/com/android/overlaytest/OverlayTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.overlaytest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.overlaytest.non_system.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * This test class is to verify overlay behavior for non-system apps.
+ */
+@RunWith(JUnit4.class)
+public class OverlayTest {
+ @Test
+ public void testStringOverlay() throws Throwable {
+ final LayoutInflater inflater = LayoutInflater.from(InstrumentationRegistry.getContext());
+ final View layout = inflater.inflate(R.layout.layout, null);
+ TextView tv = layout.findViewById(R.id.text_view_id);
+ assertNotNull(tv);
+ assertEquals("Overlaid", tv.getText().toString());
+ }
+}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp b/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/Android.bp
index 60ee6d540dc8..b5e6d9c692bd 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp
+++ b/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/Android.bp
@@ -1,18 +1,16 @@
-//
-// Copyright 2019, 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
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// 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 {
// See: http://go/android-license-faq
@@ -23,8 +21,10 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-runtime_resource_overlay {
- name: "NavigationBarModeGesturalOverlayWideBack",
- theme: "NavigationBarModeGesturalWideBack",
- product_specific: true,
+android_test {
+ name: "OverlayDeviceTestsNonSystem_AppOverlay",
+ team: "trendy_team_android_resources",
+ sdk_version: "current",
+ certificate: "platform",
+ aaptflags: ["--no-resource-removal"],
}
diff --git a/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/AndroidManifest.xml b/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/AndroidManifest.xml
new file mode 100644
index 000000000000..4df80c085602
--- /dev/null
+++ b/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.overlaytest.non_system.app_overlay"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application android:hasCode="false" />
+ <overlay android:targetPackage="com.android.overlaytest.non_system"
+ android:targetName="TestResources"
+ android:isStatic="true"
+ android:resourcesMap="@xml/overlays"/>
+</manifest> \ No newline at end of file
diff --git a/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/res/xml/overlays.xml b/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/res/xml/overlays.xml
new file mode 100644
index 000000000000..d0d4bfed94ed
--- /dev/null
+++ b/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/res/xml/overlays.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.
+ -->
+<overlay>
+ <item target="string/test_string" value="Overlaid"/>
+</overlay> \ No newline at end of file
diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb
index 826adc39c19a..97147a01b259 100644
--- a/data/etc/core.protolog.pb
+++ b/data/etc/core.protolog.pb
Binary files differ
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index d410d5f5400e..483b6934ee8c 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -709,18 +709,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
- "2959074735946674755": {
- "message": "Trying to update display configuration for system\/invalid process.",
- "level": "WARN",
- "group": "WM_DEBUG_CONFIGURATION",
- "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
- },
- "5668810920995272206": {
- "message": "Trying to update display configuration for invalid process, pid=%d",
- "level": "WARN",
- "group": "WM_DEBUG_CONFIGURATION",
- "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
- },
"-1123414663662718691": {
"message": "setVr2dDisplayId called for: %d",
"level": "DEBUG",
@@ -1057,12 +1045,6 @@
"group": "WM_DEBUG_BACK_PREVIEW",
"at": "com\/android\/server\/wm\/BackNavigationController.java"
},
- "-1459414342866553129": {
- "message": "Current focused window being animated by recents. Overriding back callback to recents controller callback.",
- "level": "DEBUG",
- "group": "WM_DEBUG_BACK_PREVIEW",
- "at": "com\/android\/server\/wm\/BackNavigationController.java"
- },
"2881085074175114605": {
"message": "Focused window didn't have a valid surface drawn.",
"level": "DEBUG",
@@ -3469,6 +3451,12 @@
"group": "WM_DEBUG_WALLPAPER",
"at": "com\/android\/server\/wm\/WallpaperController.java"
},
+ "257349083882992098": {
+ "message": "updateWallpaperTokens requestedVisibility=%b on keyguardLocked=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"7408402065665963407": {
"message": "Wallpaper at display %d - visibility: %b, keyguardLocked: %b",
"level": "VERBOSE",
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index f9347ee6ea09..e8b4104a33bb 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -424,12 +424,14 @@ key 580 APP_SWITCH
key 582 VOICE_ASSIST
# Linux KEY_ASSISTANT
key 583 ASSIST
+key 585 EMOJI_PICKER
key 656 MACRO_1
key 657 MACRO_2
key 658 MACRO_3
key 659 MACRO_4
# Keys defined by HID usages
+key usage 0x0c0065 SCREENSHOT FALLBACK_USAGE_MAPPING
key usage 0x0c0067 WINDOW FALLBACK_USAGE_MAPPING
key usage 0x0c006F BRIGHTNESS_UP FALLBACK_USAGE_MAPPING
key usage 0x0c0070 BRIGHTNESS_DOWN FALLBACK_USAGE_MAPPING
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index efbbfc23736f..24aea371c094 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -229,4 +229,24 @@ public class AndroidKeyStoreMaintenance {
"Keystore error while trying to get apps affected by SID.");
}
}
+
+ /**
+ * Deletes all keys in all KeyMint devices.
+ * Called by RecoverySystem before rebooting to recovery in order to delete all KeyMint keys,
+ * including synthetic password protector keys (used by LockSettingsService), as well as keys
+ * protecting DE and metadata encryption keys (used by vold). This ensures that FBE-encrypted
+ * data is unrecoverable even if the data wipe in recovery is interrupted or skipped.
+ */
+ public static void deleteAllKeys() throws KeyStoreException {
+ StrictMode.noteDiskWrite();
+ try {
+ getService().deleteAllKeys();
+ } catch (RemoteException | NullPointerException e) {
+ throw new KeyStoreException(SYSTEM_ERROR,
+ "Failure to connect to Keystore while trying to delete all keys.");
+ } catch (ServiceSpecificException e) {
+ throw new KeyStoreException(e.errorCode,
+ "Keystore error while trying to delete all keys.");
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index e73d8802f0b2..8487e3792993 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -18,6 +18,7 @@ package com.android.wm.shell.bubbles
import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutInfo
+import android.content.res.Resources
import android.graphics.Insets
import android.graphics.PointF
import android.graphics.Rect
@@ -43,6 +44,9 @@ class BubblePositionerTest {
private lateinit var positioner: BubblePositioner
private val context = ApplicationProvider.getApplicationContext<Context>()
+ private val resources: Resources
+ get() = context.resources
+
private val defaultDeviceConfig =
DeviceConfig(
windowBounds = Rect(0, 0, 1000, 2000),
@@ -205,6 +209,58 @@ class BubblePositionerTest {
}
@Test
+ fun testBubbleBarExpandedViewHeightAndWidth() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ // portrait orientation
+ isLandscape = false,
+ isLargeScreen = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ val bubbleBarBounds = Rect(1700, 2500, 1780, 2600)
+
+ positioner.setShowingInBubbleBar(true)
+ positioner.update(deviceConfig)
+ positioner.bubbleBarBounds = bubbleBarBounds
+
+ val spaceBetweenTopInsetAndBubbleBarInLandscape = 1680
+ val expandedViewVerticalSpacing =
+ resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
+ val expectedHeight =
+ spaceBetweenTopInsetAndBubbleBarInLandscape - 2 * expandedViewVerticalSpacing
+ val expectedWidth = resources.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width)
+
+ assertThat(positioner.getExpandedViewWidthForBubbleBar(false)).isEqualTo(expectedWidth)
+ assertThat(positioner.getExpandedViewHeightForBubbleBar(false)).isEqualTo(expectedHeight)
+ }
+
+ @Test
+ fun testBubbleBarExpandedViewHeightAndWidth_screenWidthTooSmall() {
+ val screenWidth = 300
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ // portrait orientation
+ isLandscape = false,
+ isLargeScreen = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, screenWidth, 2600)
+ )
+ val bubbleBarBounds = Rect(100, 2500, 280, 2550)
+ positioner.setShowingInBubbleBar(true)
+ positioner.update(deviceConfig)
+ positioner.bubbleBarBounds = bubbleBarBounds
+
+ val spaceBetweenTopInsetAndBubbleBarInLandscape = 180
+ val expandedViewSpacing =
+ resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
+ val expectedHeight = spaceBetweenTopInsetAndBubbleBarInLandscape - 2 * expandedViewSpacing
+ val expectedWidth = screenWidth - 15 /* horizontal insets */ - 2 * expandedViewSpacing
+ assertThat(positioner.getExpandedViewWidthForBubbleBar(false)).isEqualTo(expectedWidth)
+ assertThat(positioner.getExpandedViewHeightForBubbleBar(false)).isEqualTo(expectedHeight)
+ }
+
+ @Test
fun testGetExpandedViewHeight_max() {
val deviceConfig =
defaultDeviceConfig.copy(
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
index ef7478c04dda..c0ff1922edc8 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
@@ -22,14 +22,15 @@
android:layout_height="wrap_content"
android:gravity="center_horizontal">
- <ImageButton
+ <com.android.wm.shell.windowdecor.HandleImageButton
android:id="@+id/caption_handle"
android:layout_width="@dimen/desktop_mode_fullscreen_decor_caption_width"
android:layout_height="@dimen/desktop_mode_fullscreen_decor_caption_height"
android:paddingVertical="16dp"
+ android:paddingHorizontal="10dp"
android:contentDescription="@string/handle_text"
android:src="@drawable/decor_handle_dark"
tools:tint="@color/desktop_mode_caption_handle_bar_dark"
android:scaleType="fitXY"
- android:background="?android:selectableItemBackground"/>
+ android:background="@android:color/transparent"/>
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 39dd4d3af98d..4ee2c1a1da60 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -254,6 +254,8 @@
<dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen>
<!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. -->
<dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen>
+ <!-- Width of the expanded bubble bar view shown when the bubble is expanded. -->
+ <dimen name="bubble_bar_expanded_view_width">412dp</dimen>
<!-- Minimum width of the bubble bar manage menu. -->
<dimen name="bubble_bar_manage_menu_min_width">200dp</dimen>
<!-- Size of the dismiss icon in the bubble bar manage menu. -->
@@ -423,8 +425,9 @@
<!-- Height of desktop mode caption for fullscreen tasks. -->
<dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen>
- <!-- Width of desktop mode caption for fullscreen tasks. -->
- <dimen name="desktop_mode_fullscreen_decor_caption_width">128dp</dimen>
+ <!-- Width of desktop mode caption for fullscreen tasks.
+ 80 dp for handle + 20 dp for room to grow on the sides when hovered. -->
+ <dimen name="desktop_mode_fullscreen_decor_caption_width">100dp</dimen>
<!-- Required empty space to be visible for partially offscreen tasks. -->
<dimen name="freeform_required_visible_empty_space_in_header">48dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 97bf8f7bf459..73b2656d596a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -32,6 +32,7 @@ import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.res.Configuration;
import android.database.ContentObserver;
import android.hardware.input.InputManager;
import android.net.Uri;
@@ -71,6 +72,7 @@ import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -81,7 +83,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
/**
* Controls the window animation run when a user initiates a back gesture.
*/
-public class BackAnimationController implements RemoteCallable<BackAnimationController> {
+public class BackAnimationController implements RemoteCallable<BackAnimationController>,
+ ConfigurationChangeListener {
private static final String TAG = "ShellBackPreview";
private static final int SETTING_VALUE_OFF = 0;
private static final int SETTING_VALUE_ON = 1;
@@ -248,6 +251,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
+ mShellController.addConfigurationChangeListener(this);
}
private void setupAnimationDeveloperSettingsObserver(
@@ -297,6 +301,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private final BackAnimationImpl mBackAnimation = new BackAnimationImpl();
@Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ mShellBackAnimationRegistry.onConfigurationChanged(newConfig);
+ }
+
+ @Override
public Context getContext() {
return mContext;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index c6d46207b119..112ed0941122 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -68,7 +68,7 @@ class CrossActivityBackAnimation @Inject constructor(
private val backAnimRect = Rect()
- private val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+ private var cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
private val backAnimationRunner = BackAnimationRunner(
Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY
@@ -94,6 +94,10 @@ class CrossActivityBackAnimation @Inject constructor(
private var scrimLayer: SurfaceControl? = null
private var maxScrimAlpha: Float = 0f
+ override fun onConfigurationChanged(newConfiguration: Configuration) {
+ cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+ }
+
override fun getRunner() = backAnimationRunner
private fun startBackAnimation(backMotionEvent: BackMotionEvent) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 987001d66219..c34f30df33a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -29,6 +29,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -80,7 +81,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
private static final int POST_ANIMATION_DURATION_MS = 500;
private final Rect mStartTaskRect = new Rect();
- private final float mCornerRadius;
+ private float mCornerRadius;
// The closing window properties.
private final Rect mClosingStartRect = new Rect();
@@ -120,6 +121,11 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
mContext = context;
}
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
+ }
+
private static float mapRange(float value, float min, float max) {
return min + (value * (max - min));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index e33aa7568d09..838dab43d6e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -29,6 +29,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.RemoteException;
@@ -63,7 +64,7 @@ import javax.inject.Inject;
public class CustomizeActivityAnimation extends ShellBackAnimation {
private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
private final BackAnimationRunner mBackAnimationRunner;
- private final float mCornerRadius;
+ private float mCornerRadius;
private final SurfaceControl.Transaction mTransaction;
private final BackAnimationBackground mBackground;
private RemoteAnimationTarget mEnteringTarget;
@@ -88,6 +89,7 @@ public class CustomizeActivityAnimation extends ShellBackAnimation {
final Transformation mTransformation = new Transformation();
private final Choreographer mChoreographer;
+ private final Context mContext;
@Inject
public CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
@@ -108,6 +110,12 @@ public class CustomizeActivityAnimation extends ShellBackAnimation {
.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
mTransaction = transaction == null ? new SurfaceControl.Transaction() : transaction;
mChoreographer = choreographer != null ? choreographer : Choreographer.getInstance();
+ mContext = context;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
}
private float getLatestProgress() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
index dc659197848e..8a0daaa72e24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.back;
+import android.content.res.Configuration;
import android.window.BackNavigationInfo;
import javax.inject.Qualifier;
@@ -48,4 +49,8 @@ public abstract class ShellBackAnimation {
public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
return false;
}
+
+ void onConfigurationChanged(Configuration newConfig) {
+
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
index 26d20972c751..00daddc13346 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.back;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.res.Configuration;
import android.util.Log;
import android.util.SparseArray;
import android.window.BackNavigationInfo;
@@ -29,6 +30,7 @@ public class ShellBackAnimationRegistry {
private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
private final ShellBackAnimation mDefaultCrossActivityAnimation;
private final ShellBackAnimation mCustomizeActivityAnimation;
+ private final ShellBackAnimation mCrossTaskAnimation;
public ShellBackAnimationRegistry(
@ShellBackAnimation.CrossActivity @Nullable ShellBackAnimation crossActivityAnimation,
@@ -57,6 +59,7 @@ public class ShellBackAnimationRegistry {
mDefaultCrossActivityAnimation = crossActivityAnimation;
mCustomizeActivityAnimation = customizeActivityAnimation;
+ mCrossTaskAnimation = crossTaskAnimation;
// TODO(b/236760237): register dialog close animation when it's completed.
}
@@ -125,6 +128,12 @@ public class ShellBackAnimationRegistry {
BackNavigationInfo.TYPE_CROSS_ACTIVITY, mDefaultCrossActivityAnimation.getRunner());
}
+ void onConfigurationChanged(Configuration newConfig) {
+ mCustomizeActivityAnimation.onConfigurationChanged(newConfig);
+ mDefaultCrossActivityAnimation.onConfigurationChanged(newConfig);
+ mCrossTaskAnimation.onConfigurationChanged(newConfig);
+ }
+
BackAnimationRunner getAnimationRunnerAndInit(BackNavigationInfo backNavigationInfo) {
int type = backNavigationInfo.getType();
// Initiate customized cross-activity animation, or fall back to cross activity animation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 313d0d24b459..d2958779c0d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -455,8 +455,7 @@ public class BubbleController implements ConfigurationChangeListener,
ProtoLog.d(WM_SHELL_BUBBLES,
"onActivityRestartAttempt - taskId=%d selecting matching bubble=%s",
task.taskId, b.getKey());
- mBubbleData.setSelectedBubble(b);
- mBubbleData.setExpanded(true);
+ mBubbleData.setSelectedBubbleAndExpandStack(b);
return;
}
}
@@ -593,13 +592,6 @@ public class BubbleController implements ConfigurationChangeListener,
}
}
- private void openBubbleOverflow() {
- ensureBubbleViewsAndWindowCreated();
- mBubbleData.setShowingOverflow(true);
- mBubbleData.setSelectedBubble(mBubbleData.getOverflow());
- mBubbleData.setExpanded(true);
- }
-
/**
* Called when the status bar has become visible or invisible (either permanently or
* temporarily).
@@ -1247,8 +1239,7 @@ public class BubbleController implements ConfigurationChangeListener,
}
if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) {
// already in the stack
- mBubbleData.setSelectedBubble(b);
- mBubbleData.setExpanded(true);
+ mBubbleData.setSelectedBubbleAndExpandStack(b);
} else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
// promote it out of the overflow
promoteBubbleFromOverflow(b);
@@ -1273,8 +1264,7 @@ public class BubbleController implements ConfigurationChangeListener,
String key = entry.getKey();
Bubble bubble = mBubbleData.getBubbleInStackWithKey(key);
if (bubble != null) {
- mBubbleData.setSelectedBubble(bubble);
- mBubbleData.setExpanded(true);
+ mBubbleData.setSelectedBubbleAndExpandStack(bubble);
} else {
bubble = mBubbleData.getOverflowBubbleWithKey(key);
if (bubble != null) {
@@ -1367,8 +1357,7 @@ public class BubbleController implements ConfigurationChangeListener,
} else {
// App bubble is not selected, select it & expand
Log.i(TAG, " showOrHideAppBubble, expand and select existing app bubble");
- mBubbleData.setSelectedBubble(existingAppBubble);
- mBubbleData.setExpanded(true);
+ mBubbleData.setSelectedBubbleAndExpandStack(existingAppBubble);
}
} else {
// Check if it exists in the overflow
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 61f0ed22b537..ae3d0c559014 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -365,6 +365,19 @@ public class BubbleData {
mSelectedBubble = bubble;
}
+ /**
+ * Sets the selected bubble and expands it.
+ *
+ * <p>This dispatches a single state update for both changes and should be used instead of
+ * calling {@link #setSelectedBubble(BubbleViewProvider)} followed by
+ * {@link #setExpanded(boolean)} immediately after, which will generate 2 separate updates.
+ */
+ public void setSelectedBubbleAndExpandStack(BubbleViewProvider bubble) {
+ setSelectedBubbleInternal(bubble);
+ setExpandedInternal(true);
+ dispatchPendingChanges();
+ }
+
public void setSelectedBubble(BubbleViewProvider bubble) {
setSelectedBubbleInternal(bubble);
dispatchPendingChanges();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 4d5e516f76e5..14c3a0701c83 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -149,9 +149,10 @@ public class BubblePositioner {
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
if (mShowingInBubbleBar) {
- mExpandedViewLargeScreenWidth = isLandscape()
- ? (int) (bounds.width() * EXPANDED_VIEW_BUBBLE_BAR_LANDSCAPE_WIDTH_PERCENT)
- : (int) (bounds.width() * EXPANDED_VIEW_BUBBLE_BAR_PORTRAIT_WIDTH_PERCENT);
+ mExpandedViewLargeScreenWidth = Math.min(
+ res.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width),
+ mPositionRect.width() - 2 * mExpandedViewPadding
+ );
} else if (mDeviceConfig.isSmallTablet()) {
mExpandedViewLargeScreenWidth = (int) (bounds.width()
* EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT);
@@ -839,11 +840,42 @@ public class BubblePositioner {
* How tall the expanded view should be when showing from the bubble bar.
*/
public int getExpandedViewHeightForBubbleBar(boolean isOverflow) {
- return isOverflow
- ? mOverflowHeight
- : getExpandedViewBottomForBubbleBar() - mInsets.top - mExpandedViewPadding;
+ if (isOverflow) {
+ return mOverflowHeight;
+ } else {
+ return getBubbleBarExpandedViewHeightForLandscape();
+ }
}
+ /**
+ * Calculate the height of expanded view in landscape mode regardless current orientation.
+ * Here is an explanation:
+ * ------------------------ mScreenRect.top
+ * | top inset ↕ |
+ * |-----------------------
+ * | 16dp spacing ↕ |
+ * | --------- | --- expanded view top
+ * | | | | ↑
+ * | | | | ↓ expanded view height
+ * | --------- | --- expanded view bottom
+ * | 16dp spacing ↕ | ↑
+ * | @bubble bar@ | | height of the bubble bar container
+ * ------------------------ | already includes bottom inset and spacing
+ * | bottom inset ↕ | ↓
+ * |----------------------| --- mScreenRect.bottom
+ */
+ private int getBubbleBarExpandedViewHeightForLandscape() {
+ int heightOfBubbleBarContainer =
+ mScreenRect.height() - getExpandedViewBottomForBubbleBar();
+ // getting landscape height from screen rect
+ int expandedViewHeight = Math.min(mScreenRect.width(), mScreenRect.height());
+ expandedViewHeight -= heightOfBubbleBarContainer; /* removing bubble container height */
+ expandedViewHeight -= mInsets.top; /* removing top inset */
+ expandedViewHeight -= mExpandedViewPadding; /* removing spacing */
+ return expandedViewHeight;
+ }
+
+
/** The bottom position of the expanded view when showing above the bubble bar. */
public int getExpandedViewBottomForBubbleBar() {
return mBubbleBarBounds.top - mExpandedViewPadding;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index b86e39fca742..4eff3f03670e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -23,19 +23,25 @@ import android.os.Handler;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipPerfHintController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
+import com.android.wm.shell.pip2.phone.PipMotionHelper;
import com.android.wm.shell.pip2.phone.PipScheduler;
+import com.android.wm.shell.pip2.phone.PipTouchHandler;
import com.android.wm.shell.pip2.phone.PipTransition;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellController;
@@ -62,6 +68,7 @@ public abstract class Pip2Module {
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
Optional<PipController> pipController,
+ PipTouchHandler pipTouchHandler,
@NonNull PipScheduler pipScheduler) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
pipBoundsState, null, pipBoundsAlgorithm, pipScheduler);
@@ -109,4 +116,34 @@ public abstract class Pip2Module {
return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
systemWindows, pipUiEventLogger, mainExecutor, mainHandler);
}
+
+
+ @WMSingleton
+ @Provides
+ static PipTouchHandler providePipTouchHandler(Context context,
+ ShellInit shellInit,
+ PhonePipMenuController menuPhoneController,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ @NonNull PipBoundsState pipBoundsState,
+ @NonNull SizeSpecSource sizeSpecSource,
+ PipMotionHelper pipMotionHelper,
+ FloatingContentCoordinator floatingContentCoordinator,
+ PipUiEventLogger pipUiEventLogger,
+ @ShellMainThread ShellExecutor mainExecutor,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+ return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
+ pipBoundsState, sizeSpecSource, pipMotionHelper, floatingContentCoordinator,
+ pipUiEventLogger, mainExecutor, pipPerfHintControllerOptional);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipMotionHelper providePipMotionHelper(Context context,
+ PipBoundsState pipBoundsState, PhonePipMenuController menuController,
+ PipSnapAlgorithm pipSnapAlgorithm,
+ FloatingContentCoordinator floatingContentCoordinator,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+ return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm,
+ floatingContentCoordinator, pipPerfHintControllerOptional);
+ }
}
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 e210ea731f7a..58942ec92a71 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
@@ -1354,6 +1354,13 @@ class DesktopTasksController(
"setTaskListener"
) { _ -> listener?.let { remoteListener.register(it) } ?: remoteListener.unregister() }
}
+
+ override fun moveToDesktop(taskId: Int) {
+ ExecutorUtils.executeRemoteCallWithTaskPermission(
+ controller,
+ "moveToDesktop"
+ ) { c -> c.moveToDesktop(taskId) }
+ }
}
companion object {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 6bdaf1eadb8a..fa4352241193 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -45,4 +45,7 @@ interface IDesktopMode {
/** Set listener that will receive callbacks about updates to desktop tasks */
oneway void setTaskListener(IDesktopTaskListener listener);
+
+ /** Move a task with given `taskId` to desktop */
+ void moveToDesktop(int taskId);
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
new file mode 100644
index 000000000000..e7e797096c0e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.bubbles.DismissCircleView;
+import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+
+import kotlin.Unit;
+
+/**
+ * Handler of all Magnetized Object related code for PiP.
+ */
+public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListener {
+
+ /* The multiplier to apply scale the target size by when applying the magnetic field radius */
+ private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f;
+
+ /**
+ * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
+ * PIP.
+ */
+ private MagnetizedObject<Rect> mMagnetizedPip;
+
+ /**
+ * Container for the dismiss circle, so that it can be animated within the container via
+ * translation rather than within the WindowManager via slow layout animations.
+ */
+ private DismissView mTargetViewContainer;
+
+ /** Circle view used to render the dismiss target. */
+ private DismissCircleView mTargetView;
+
+ /**
+ * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
+ */
+ private MagnetizedObject.MagneticTarget mMagneticTarget;
+
+ // Allow dragging the PIP to a location to close it
+ private boolean mEnableDismissDragToEdge;
+
+ private int mTargetSize;
+ private int mDismissAreaHeight;
+ private float mMagneticFieldRadiusPercent = 1f;
+ private WindowInsets mWindowInsets;
+
+ private SurfaceControl mTaskLeash;
+ private boolean mHasDismissTargetSurface;
+
+ private final Context mContext;
+ private final PipMotionHelper mMotionHelper;
+ private final PipUiEventLogger mPipUiEventLogger;
+ private final WindowManager mWindowManager;
+ private final ShellExecutor mMainExecutor;
+
+ public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger,
+ PipMotionHelper motionHelper, ShellExecutor mainExecutor) {
+ mContext = context;
+ mPipUiEventLogger = pipUiEventLogger;
+ mMotionHelper = motionHelper;
+ mMainExecutor = mainExecutor;
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ void init() {
+ Resources res = mContext.getResources();
+ mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
+ mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
+
+ if (mTargetViewContainer != null) {
+ // init can be called multiple times, remove the old one from view hierarchy first.
+ cleanUpDismissTarget();
+ }
+
+ mTargetViewContainer = new DismissView(mContext);
+ DismissViewUtils.setup(mTargetViewContainer);
+ mTargetView = mTargetViewContainer.getCircle();
+ mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> {
+ if (!windowInsets.equals(mWindowInsets)) {
+ mWindowInsets = windowInsets;
+ updateMagneticTargetSize();
+ }
+ return windowInsets;
+ });
+
+ mMagnetizedPip = mMotionHelper.getMagnetizedPip();
+ mMagnetizedPip.clearAllTargets();
+ mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
+ updateMagneticTargetSize();
+
+ mMagnetizedPip.setAnimateStuckToTarget(
+ (target, velX, velY, flung, after) -> {
+ if (mEnableDismissDragToEdge) {
+ mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
+ }
+ return Unit.INSTANCE;
+ });
+ mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
+ // Show the dismiss target, in case the initial touch event occurred within
+ // the magnetic field radius.
+ if (mEnableDismissDragToEdge) {
+ showDismissTargetMaybe();
+ }
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject,
+ float velX, float velY, boolean wasFlungOut) {
+ if (wasFlungOut) {
+ mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
+ hideDismissTargetMaybe();
+ } else {
+ mMotionHelper.setSpringingToTouch(true);
+ }
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
+ if (mEnableDismissDragToEdge) {
+ mMainExecutor.executeDelayed(() -> {
+ mMotionHelper.notifyDismissalPending();
+ mMotionHelper.animateDismiss();
+ hideDismissTargetMaybe();
+
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
+ }, 0);
+ }
+ }
+ });
+
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ mTargetViewContainer.getViewTreeObserver().removeOnPreDrawListener(this);
+ mHasDismissTargetSurface = true;
+ updateDismissTargetLayer();
+ return true;
+ }
+
+ /**
+ * Potentially start consuming future motion events if PiP is currently near the magnetized
+ * object.
+ */
+ public boolean maybeConsumeMotionEvent(MotionEvent ev) {
+ return mMagnetizedPip.maybeConsumeMotionEvent(ev);
+ }
+
+ /**
+ * Update the magnet size.
+ */
+ public void updateMagneticTargetSize() {
+ if (mTargetView == null) {
+ return;
+ }
+ if (mTargetViewContainer != null) {
+ mTargetViewContainer.updateResources();
+ }
+
+ final Resources res = mContext.getResources();
+ mTargetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
+ mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
+
+ // Set the magnetic field radius equal to the target size from the center of the target
+ setMagneticFieldRadiusPercent(mMagneticFieldRadiusPercent);
+ }
+
+ /**
+ * Increase or decrease the field radius of the magnet object, e.g. with larger percent,
+ * PiP will magnetize to the field sooner.
+ */
+ public void setMagneticFieldRadiusPercent(float percent) {
+ mMagneticFieldRadiusPercent = percent;
+ mMagneticTarget.setMagneticFieldRadiusPx((int) (mMagneticFieldRadiusPercent * mTargetSize
+ * MAGNETIC_FIELD_RADIUS_MULTIPLIER));
+ }
+
+ public void setTaskLeash(SurfaceControl taskLeash) {
+ mTaskLeash = taskLeash;
+ }
+
+ private void updateDismissTargetLayer() {
+ if (!mHasDismissTargetSurface || mTaskLeash == null) {
+ // No dismiss target surface, can just return
+ return;
+ }
+
+ final SurfaceControl targetViewLeash =
+ mTargetViewContainer.getViewRootImpl().getSurfaceControl();
+ if (!targetViewLeash.isValid()) {
+ // The surface of mTargetViewContainer is somehow not ready, bail early
+ return;
+ }
+
+ // Put the dismiss target behind the task
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setRelativeLayer(targetViewLeash, mTaskLeash, -1);
+ t.apply();
+ }
+
+ /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
+ public void createOrUpdateDismissTarget() {
+ if (mTargetViewContainer.getParent() == null) {
+ mTargetViewContainer.cancelAnimators();
+
+ mTargetViewContainer.setVisibility(View.INVISIBLE);
+ mTargetViewContainer.getViewTreeObserver().removeOnPreDrawListener(this);
+ mHasDismissTargetSurface = false;
+
+ mWindowManager.addView(mTargetViewContainer, getDismissTargetLayoutParams());
+ } else {
+ mWindowManager.updateViewLayout(mTargetViewContainer, getDismissTargetLayoutParams());
+ }
+ }
+
+ /** Returns layout params for the dismiss target, using the latest display metrics. */
+ private WindowManager.LayoutParams getDismissTargetLayoutParams() {
+ final Point windowSize = new Point();
+ mWindowManager.getDefaultDisplay().getRealSize(windowSize);
+ int height = Math.min(windowSize.y, mDismissAreaHeight);
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ height,
+ 0, windowSize.y - height,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+
+ lp.setTitle("pip-dismiss-overlay");
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ lp.setFitInsetsTypes(0 /* types */);
+
+ return lp;
+ }
+
+ /** Makes the dismiss target visible and animates it in, if it isn't already visible. */
+ public void showDismissTargetMaybe() {
+ if (!mEnableDismissDragToEdge) {
+ return;
+ }
+
+ createOrUpdateDismissTarget();
+
+ if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
+ mTargetViewContainer.getViewTreeObserver().addOnPreDrawListener(this);
+ }
+ // always invoke show, since the target might still be VISIBLE while playing hide animation,
+ // so we want to ensure it will show back again
+ mTargetViewContainer.show();
+ }
+
+ /** Animates the magnetic dismiss target out and then sets it to GONE. */
+ public void hideDismissTargetMaybe() {
+ if (!mEnableDismissDragToEdge) {
+ return;
+ }
+ mTargetViewContainer.hide();
+ }
+
+ /**
+ * Removes the dismiss target and cancels any pending callbacks to show it.
+ */
+ public void cleanUpDismissTarget() {
+ if (mTargetViewContainer.getParent() != null) {
+ mWindowManager.removeViewImmediate(mTargetViewContainer);
+ }
+ }
+}
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
new file mode 100644
index 000000000000..619bed4e19ca
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_BOUNCY;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
+
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
+import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_DISMISS;
+import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Debug;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.FloatProperties;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPerfHintController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
+
+import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * A helper to animate and manipulate the PiP.
+ */
+public class PipMotionHelper implements PipAppOpsListener.Callback,
+ FloatingContentCoordinator.FloatingContent {
+ private static final String TAG = "PipMotionHelper";
+ private static final boolean DEBUG = false;
+
+ private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
+ private static final int EXPAND_STACK_TO_MENU_DURATION = 250;
+ private static final int UNSTASH_DURATION = 250;
+ private static final int LEAVE_PIP_DURATION = 300;
+ private static final int SHIFT_DURATION = 300;
+
+ /** Friction to use for PIP when it moves via physics fling animations. */
+ private static final float DEFAULT_FRICTION = 1.9f;
+ /** How much of the dismiss circle size to use when scaling down PIP. **/
+ private static final float DISMISS_CIRCLE_PERCENT = 0.85f;
+
+ private final Context mContext;
+ private @NonNull PipBoundsState mPipBoundsState;
+
+ private PhonePipMenuController mMenuController;
+ private PipSnapAlgorithm mSnapAlgorithm;
+
+ /** The region that all of PIP must stay within. */
+ private final Rect mFloatingAllowedArea = new Rect();
+
+ /** Coordinator instance for resolving conflicts with other floating content. */
+ private FloatingContentCoordinator mFloatingContentCoordinator;
+
+ @Nullable private final PipPerfHintController mPipPerfHintController;
+ @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
+ /**
+ * PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()}
+ * using physics animations.
+ */
+ private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator;
+
+ private MagnetizedObject<Rect> mMagnetizedPip;
+
+ /**
+ * Update listener that resizes the PIP to {@link PipBoundsState#getMotionBoundsState()}.
+ */
+ private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener;
+
+ /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
+ private PhysicsAnimator.FlingConfig mFlingConfigX;
+ private PhysicsAnimator.FlingConfig mFlingConfigY;
+ /** FlingConfig instances provided to PhysicsAnimator for stashing. */
+ private PhysicsAnimator.FlingConfig mStashConfigX;
+
+ /** SpringConfig to use for fling-then-spring animations. */
+ private final PhysicsAnimator.SpringConfig mSpringConfig =
+ new PhysicsAnimator.SpringConfig(700f, DAMPING_RATIO_NO_BOUNCY);
+
+ /** SpringConfig used for animating into the dismiss region, matches the one in
+ * {@link MagnetizedObject}. */
+ private final PhysicsAnimator.SpringConfig mAnimateToDismissSpringConfig =
+ new PhysicsAnimator.SpringConfig(STIFFNESS_MEDIUM, DAMPING_RATIO_NO_BOUNCY);
+
+ /** SpringConfig used for animating the pip to catch up to the finger once it leaves the dismiss
+ * drag region. */
+ private final PhysicsAnimator.SpringConfig mCatchUpSpringConfig =
+ new PhysicsAnimator.SpringConfig(5000f, DAMPING_RATIO_NO_BOUNCY);
+
+ /** SpringConfig to use for springing PIP away from conflicting floating content. */
+ private final PhysicsAnimator.SpringConfig mConflictResolutionSpringConfig =
+ new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_NO_BOUNCY);
+
+ private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
+ if (mPipBoundsState.getBounds().equals(newBounds)) {
+ return;
+ }
+
+ mMenuController.updateMenuLayout(newBounds);
+ mPipBoundsState.setBounds(newBounds);
+ };
+
+ /**
+ * Whether we're springing to the touch event location (vs. moving it to that position
+ * instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was
+ * 'stuck' in the target and needs to catch up to the touch location.
+ */
+ private boolean mSpringingToTouch = false;
+
+ /**
+ * Whether PIP was released in the dismiss target, and will be animated out and dismissed
+ * shortly.
+ */
+ private boolean mDismissalPending = false;
+
+ /**
+ * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is
+ * used to show menu activity when the expand animation is completed.
+ */
+ private Runnable mPostPipTransitionCallback;
+
+ public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
+ PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm,
+ FloatingContentCoordinator floatingContentCoordinator,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+ mContext = context;
+ mPipBoundsState = pipBoundsState;
+ mMenuController = menuController;
+ mSnapAlgorithm = snapAlgorithm;
+ mFloatingContentCoordinator = floatingContentCoordinator;
+ mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
+ mResizePipUpdateListener = (target, values) -> {
+ if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
+ /*
+ mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
+ mPipBoundsState.getMotionBoundsState().getBoundsInMotion(), null);
+ */
+ }
+ };
+ }
+
+ void init() {
+ mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
+ mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+ }
+
+ @NonNull
+ @Override
+ public Rect getFloatingBoundsOnScreen() {
+ return !mPipBoundsState.getMotionBoundsState().getAnimatingToBounds().isEmpty()
+ ? mPipBoundsState.getMotionBoundsState().getAnimatingToBounds() : getBounds();
+ }
+
+ @NonNull
+ @Override
+ public Rect getAllowedFloatingBoundsRegion() {
+ return mFloatingAllowedArea;
+ }
+
+ @Override
+ public void moveToBounds(@NonNull Rect bounds) {
+ animateToBounds(bounds, mConflictResolutionSpringConfig);
+ }
+
+ /**
+ * Synchronizes the current bounds with the pinned stack, cancelling any ongoing animations.
+ */
+ void synchronizePinnedStackBounds() {
+ cancelPhysicsAnimation();
+ mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
+
+ /*
+ if (mPipTaskOrganizer.isInPip()) {
+ mFloatingContentCoordinator.onContentMoved(this);
+ }
+ */
+ }
+
+ /**
+ * Tries to move the pinned stack to the given {@param bounds}.
+ */
+ void movePip(Rect toBounds) {
+ movePip(toBounds, false /* isDragging */);
+ }
+
+ /**
+ * Tries to move the pinned stack to the given {@param bounds}.
+ *
+ * @param isDragging Whether this movement is the result of a drag touch gesture. If so, we
+ * won't notify the floating content coordinator of this move, since that will
+ * happen when the gesture ends.
+ */
+ void movePip(Rect toBounds, boolean isDragging) {
+ if (!isDragging) {
+ mFloatingContentCoordinator.onContentMoved(this);
+ }
+
+ if (!mSpringingToTouch) {
+ // If we are moving PIP directly to the touch event locations, cancel any animations and
+ // move PIP to the given bounds.
+ cancelPhysicsAnimation();
+
+ if (!isDragging) {
+ resizePipUnchecked(toBounds);
+ mPipBoundsState.setBounds(toBounds);
+ } else {
+ mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds);
+ /*
+ mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds,
+ (Rect newBounds) -> {
+ mMenuController.updateMenuLayout(newBounds);
+ });
+ */
+ }
+ } else {
+ // If PIP is 'catching up' after being stuck in the dismiss target, update the animation
+ // to spring towards the new touch location.
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mCatchUpSpringConfig)
+ .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mCatchUpSpringConfig)
+ .spring(FloatProperties.RECT_X, toBounds.left, mCatchUpSpringConfig)
+ .spring(FloatProperties.RECT_Y, toBounds.top, mCatchUpSpringConfig);
+
+ startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */);
+ }
+ }
+
+ /** Animates the PIP into the dismiss target, scaling it down. */
+ void animateIntoDismissTarget(
+ MagnetizedObject.MagneticTarget target,
+ float velX, float velY,
+ boolean flung, Function0<Unit> after) {
+ final PointF targetCenter = target.getCenterOnScreen();
+
+ // PIP should fit in the circle
+ final float dismissCircleSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.dismiss_circle_size);
+
+ final float width = getBounds().width();
+ final float height = getBounds().height();
+ final float ratio = width / height;
+
+ // Width should be a little smaller than the circle size.
+ final float desiredWidth = dismissCircleSize * DISMISS_CIRCLE_PERCENT;
+ final float desiredHeight = desiredWidth / ratio;
+ final float destinationX = targetCenter.x - (desiredWidth / 2f);
+ final float destinationY = targetCenter.y - (desiredHeight / 2f);
+
+ // If we're already in the dismiss target area, then there won't be a move to set the
+ // temporary bounds, so just initialize it to the current bounds.
+ if (!mPipBoundsState.getMotionBoundsState().isInMotion()) {
+ mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds());
+ }
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_X, destinationX, velX, mAnimateToDismissSpringConfig)
+ .spring(FloatProperties.RECT_Y, destinationY, velY, mAnimateToDismissSpringConfig)
+ .spring(FloatProperties.RECT_WIDTH, desiredWidth, mAnimateToDismissSpringConfig)
+ .spring(FloatProperties.RECT_HEIGHT, desiredHeight, mAnimateToDismissSpringConfig)
+ .withEndActions(after);
+
+ startBoundsAnimator(destinationX, destinationY);
+ }
+
+ /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */
+ void setSpringingToTouch(boolean springingToTouch) {
+ mSpringingToTouch = springingToTouch;
+ }
+
+ /**
+ * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
+ * * fullscreen depending on the display area's windowing mode.
+ */
+ void expandLeavePip(boolean skipAnimation) {
+ expandLeavePip(skipAnimation, false /* enterSplit */);
+ }
+
+ /**
+ * Resizes the pinned task to split-screen mode.
+ */
+ void expandIntoSplit() {
+ expandLeavePip(false, true /* enterSplit */);
+ }
+
+ /**
+ * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
+ * fullscreen depending on the display area's windowing mode.
+ */
+ private void expandLeavePip(boolean skipAnimation, boolean enterSplit) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: exitPip: skipAnimation=%s"
+ + " callers=\n%s", TAG, skipAnimation, Debug.getCallers(5, " "));
+ }
+ cancelPhysicsAnimation();
+ mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
+ // mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION, enterSplit);
+ }
+
+ /**
+ * Dismisses the pinned stack.
+ */
+ @Override
+ public void dismissPip() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: removePip: callers=\n%s", TAG, Debug.getCallers(5, " "));
+ }
+ cancelPhysicsAnimation();
+ mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */);
+ // mPipTaskOrganizer.removePip();
+ }
+
+ /** Sets the movement bounds to use to constrain PIP position animations. */
+ void onMovementBoundsChanged() {
+ rebuildFlingConfigs();
+
+ // The movement bounds represent the area within which we can move PIP's top-left position.
+ // The allowed area for all of PIP is those bounds plus PIP's width and height.
+ mFloatingAllowedArea.set(mPipBoundsState.getMovementBounds());
+ mFloatingAllowedArea.right += getBounds().width();
+ mFloatingAllowedArea.bottom += getBounds().height();
+ }
+
+ /**
+ * @return the PiP bounds.
+ */
+ private Rect getBounds() {
+ return mPipBoundsState.getBounds();
+ }
+
+ /**
+ * Flings the PiP to the closest snap target.
+ */
+ void flingToSnapTarget(
+ float velocityX, float velocityY, @Nullable Runnable postBoundsUpdateCallback) {
+ movetoTarget(velocityX, velocityY, postBoundsUpdateCallback, false /* isStash */);
+ }
+
+ /**
+ * Stash PiP to the closest edge. We set velocityY to 0 to limit pure horizontal motion.
+ */
+ void stashToEdge(float velX, float velY, @Nullable Runnable postBoundsUpdateCallback) {
+ velY = mPipBoundsState.getStashedState() == STASH_TYPE_NONE ? 0 : velY;
+ movetoTarget(velX, velY, postBoundsUpdateCallback, true /* isStash */);
+ }
+
+ private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
+
+ private void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
+ }
+ }
+
+ private void movetoTarget(
+ float velocityX,
+ float velocityY,
+ @Nullable Runnable postBoundsUpdateCallback,
+ boolean isStash) {
+ // If we're flinging to a snap target now, we're not springing to catch up to the touch
+ // location now.
+ mSpringingToTouch = false;
+
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig)
+ .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig)
+ .flingThenSpring(
+ FloatProperties.RECT_X, velocityX,
+ isStash ? mStashConfigX : mFlingConfigX,
+ mSpringConfig, true /* flingMustReachMinOrMax */)
+ .flingThenSpring(
+ FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig);
+
+ final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
+ final float leftEdge = isStash
+ ? mPipBoundsState.getStashOffset() - mPipBoundsState.getBounds().width()
+ + insetBounds.left
+ : mPipBoundsState.getMovementBounds().left;
+ final float rightEdge = isStash
+ ? mPipBoundsState.getDisplayBounds().right - mPipBoundsState.getStashOffset()
+ - insetBounds.right
+ : mPipBoundsState.getMovementBounds().right;
+
+ final float xEndValue = velocityX < 0 ? leftEdge : rightEdge;
+
+ final int startValueY = mPipBoundsState.getMotionBoundsState().getBoundsInMotion().top;
+ final float estimatedFlingYEndValue =
+ PhysicsAnimator.estimateFlingEndValue(startValueY, velocityY, mFlingConfigY);
+
+ startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */,
+ postBoundsUpdateCallback);
+ }
+
+ /**
+ * Animates PIP to the provided bounds, using physics animations and the given spring
+ * configuration
+ */
+ void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) {
+ if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
+ // Animate from the current bounds if we're not already animating.
+ mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds());
+ }
+
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_X, bounds.left, springConfig)
+ .spring(FloatProperties.RECT_Y, bounds.top, springConfig);
+ startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */);
+ }
+
+ /**
+ * Animates the dismissal of the PiP off the edge of the screen.
+ */
+ void animateDismiss() {
+ // Animate off the bottom of the screen, then dismiss PIP.
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_Y,
+ mPipBoundsState.getMovementBounds().bottom + getBounds().height() * 2,
+ 0,
+ mSpringConfig)
+ .withEndActions(this::dismissPip);
+
+ startBoundsAnimator(
+ getBounds().left /* toX */, getBounds().bottom + getBounds().height() /* toY */);
+
+ mDismissalPending = false;
+ }
+
+ /**
+ * Animates the PiP to the expanded state to show the menu.
+ */
+ float animateToExpandedState(Rect expandedBounds, Rect movementBounds,
+ Rect expandedMovementBounds, Runnable callback) {
+ float savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(getBounds()),
+ movementBounds);
+ mSnapAlgorithm.applySnapFraction(expandedBounds, expandedMovementBounds, savedSnapFraction);
+ mPostPipTransitionCallback = callback;
+ resizeAndAnimatePipUnchecked(expandedBounds, EXPAND_STACK_TO_MENU_DURATION);
+ return savedSnapFraction;
+ }
+
+ /**
+ * Animates the PiP from the expanded state to the normal state after the menu is hidden.
+ */
+ void animateToUnexpandedState(Rect normalBounds, float savedSnapFraction,
+ Rect normalMovementBounds, Rect currentMovementBounds, boolean immediate) {
+ if (savedSnapFraction < 0f) {
+ // If there are no saved snap fractions, then just use the current bounds
+ savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(getBounds()),
+ currentMovementBounds, mPipBoundsState.getStashedState());
+ }
+
+ mSnapAlgorithm.applySnapFraction(normalBounds, normalMovementBounds, savedSnapFraction,
+ mPipBoundsState.getStashedState(), mPipBoundsState.getStashOffset(),
+ mPipBoundsState.getDisplayBounds(),
+ mPipBoundsState.getDisplayLayout().stableInsets());
+
+ if (immediate) {
+ movePip(normalBounds);
+ } else {
+ resizeAndAnimatePipUnchecked(normalBounds, SHRINK_STACK_FROM_MENU_DURATION);
+ }
+ }
+
+ /**
+ * Animates the PiP to the stashed state, choosing the closest edge.
+ */
+ void animateToStashedClosestEdge() {
+ Rect tmpBounds = new Rect();
+ final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
+ final int stashType =
+ mPipBoundsState.getBounds().left == mPipBoundsState.getMovementBounds().left
+ ? STASH_TYPE_LEFT : STASH_TYPE_RIGHT;
+ final float leftEdge = stashType == STASH_TYPE_LEFT
+ ? mPipBoundsState.getStashOffset()
+ - mPipBoundsState.getBounds().width() + insetBounds.left
+ : mPipBoundsState.getDisplayBounds().right
+ - mPipBoundsState.getStashOffset() - insetBounds.right;
+ tmpBounds.set((int) leftEdge,
+ mPipBoundsState.getBounds().top,
+ (int) (leftEdge + mPipBoundsState.getBounds().width()),
+ mPipBoundsState.getBounds().bottom);
+ resizeAndAnimatePipUnchecked(tmpBounds, UNSTASH_DURATION);
+ mPipBoundsState.setStashed(stashType);
+ }
+
+ /**
+ * Animates the PiP from stashed state into un-stashed, popping it out from the edge.
+ */
+ void animateToUnStashedBounds(Rect unstashedBounds) {
+ resizeAndAnimatePipUnchecked(unstashedBounds, UNSTASH_DURATION);
+ }
+
+ /**
+ * Animates the PiP to offset it from the IME or shelf.
+ */
+ void animateToOffset(Rect originalBounds, int offset) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: animateToOffset: originalBounds=%s offset=%s"
+ + " callers=\n%s", TAG, originalBounds, offset,
+ Debug.getCallers(5, " "));
+ }
+ cancelPhysicsAnimation();
+ /*
+ mPipTaskOrganizer.scheduleOffsetPip(originalBounds, offset, SHIFT_DURATION,
+ mUpdateBoundsCallback);
+ */
+ }
+
+ /**
+ * Cancels all existing animations.
+ */
+ private void cancelPhysicsAnimation() {
+ mTemporaryBoundsPhysicsAnimator.cancel();
+ mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
+ mSpringingToTouch = false;
+ }
+
+ /** Set new fling configs whose min/max values respect the given movement bounds. */
+ private void rebuildFlingConfigs() {
+ mFlingConfigX = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION,
+ mPipBoundsState.getMovementBounds().left,
+ mPipBoundsState.getMovementBounds().right);
+ mFlingConfigY = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION,
+ mPipBoundsState.getMovementBounds().top,
+ mPipBoundsState.getMovementBounds().bottom);
+ final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
+ mStashConfigX = new PhysicsAnimator.FlingConfig(
+ DEFAULT_FRICTION,
+ mPipBoundsState.getStashOffset() - mPipBoundsState.getBounds().width()
+ + insetBounds.left,
+ mPipBoundsState.getDisplayBounds().right - mPipBoundsState.getStashOffset()
+ - insetBounds.right);
+ }
+
+ private void startBoundsAnimator(float toX, float toY) {
+ startBoundsAnimator(toX, toY, null /* postBoundsUpdateCallback */);
+ }
+
+ /**
+ * Starts the physics animator which will update the animated PIP bounds using physics
+ * animations, as well as the TimeAnimator which will apply those bounds to PIP.
+ *
+ * This will also add end actions to the bounds animator that cancel the TimeAnimator and update
+ * the 'real' bounds to equal the final animated bounds.
+ *
+ * If one wishes to supply a callback after all the 'real' bounds update has happened,
+ * pass @param postBoundsUpdateCallback.
+ */
+ private void startBoundsAnimator(float toX, float toY, Runnable postBoundsUpdateCallback) {
+ if (!mSpringingToTouch) {
+ cancelPhysicsAnimation();
+ }
+
+ setAnimatingToBounds(new Rect(
+ (int) toX,
+ (int) toY,
+ (int) toX + getBounds().width(),
+ (int) toY + getBounds().height()));
+
+ if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
+ if (mPipPerfHintController != null) {
+ // Start a high perf session with a timeout callback.
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "startBoundsAnimator");
+ }
+ if (postBoundsUpdateCallback != null) {
+ mTemporaryBoundsPhysicsAnimator
+ .addUpdateListener(mResizePipUpdateListener)
+ .withEndActions(this::onBoundsPhysicsAnimationEnd,
+ postBoundsUpdateCallback);
+ } else {
+ mTemporaryBoundsPhysicsAnimator
+ .addUpdateListener(mResizePipUpdateListener)
+ .withEndActions(this::onBoundsPhysicsAnimationEnd);
+ }
+ }
+
+ mTemporaryBoundsPhysicsAnimator.start();
+ }
+
+ /**
+ * Notify that PIP was released in the dismiss target and will be animated out and dismissed
+ * shortly.
+ */
+ void notifyDismissalPending() {
+ mDismissalPending = true;
+ }
+
+ private void onBoundsPhysicsAnimationEnd() {
+ // The physics animation ended, though we may not necessarily be done animating, such as
+ // when we're still dragging after moving out of the magnetic target.
+ if (!mDismissalPending
+ && !mSpringingToTouch
+ && !mMagnetizedPip.getObjectStuckToTarget()) {
+ // All motion operations have actually finished.
+ mPipBoundsState.setBounds(
+ mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+ mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
+ if (!mDismissalPending) {
+ // do not schedule resize if PiP is dismissing, which may cause app re-open to
+ // mBounds instead of its normal bounds.
+ // mPipTaskOrganizer.scheduleFinishResizePip(getBounds());
+ }
+ }
+ mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
+ mSpringingToTouch = false;
+ mDismissalPending = false;
+ cleanUpHighPerfSessionMaybe();
+ }
+
+ /**
+ * Notifies the floating coordinator that we're moving, and sets the animating to bounds so
+ * we return these bounds from
+ * {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}.
+ */
+ private void setAnimatingToBounds(Rect bounds) {
+ mPipBoundsState.getMotionBoundsState().setAnimatingToBounds(bounds);
+ mFloatingContentCoordinator.onContentMoved(this);
+ }
+
+ /**
+ * Directly resizes the PiP to the given {@param bounds}.
+ */
+ private void resizePipUnchecked(Rect toBounds) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: resizePipUnchecked: toBounds=%s"
+ + " callers=\n%s", TAG, toBounds, Debug.getCallers(5, " "));
+ }
+ if (!toBounds.equals(getBounds())) {
+ // mPipTaskOrganizer.scheduleResizePip(toBounds, mUpdateBoundsCallback);
+ }
+ }
+
+ /**
+ * Directly resizes the PiP to the given {@param bounds}.
+ */
+ private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: resizeAndAnimatePipUnchecked: toBounds=%s"
+ + " duration=%s callers=\n%s", TAG, toBounds, duration,
+ Debug.getCallers(5, " "));
+ }
+
+ // Intentionally resize here even if the current bounds match the destination bounds.
+ // This is so all the proper callbacks are performed.
+
+ // mPipTaskOrganizer.scheduleAnimateResizePip(toBounds, duration,
+ // TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND, null /* updateBoundsCallback */);
+ // setAnimatingToBounds(toBounds);
+ }
+
+ /**
+ * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the
+ * magnetic dismiss target so it can calculate PIP's size and position.
+ */
+ MagnetizedObject<Rect> getMagnetizedPip() {
+ if (mMagnetizedPip == null) {
+ mMagnetizedPip = new MagnetizedObject<Rect>(
+ mContext, mPipBoundsState.getMotionBoundsState().getBoundsInMotion(),
+ FloatProperties.RECT_X, FloatProperties.RECT_Y) {
+ @Override
+ public float getWidth(@NonNull Rect animatedPipBounds) {
+ return animatedPipBounds.width();
+ }
+
+ @Override
+ public float getHeight(@NonNull Rect animatedPipBounds) {
+ return animatedPipBounds.height();
+ }
+
+ @Override
+ public void getLocationOnScreen(
+ @NonNull Rect animatedPipBounds, @NonNull int[] loc) {
+ loc[0] = animatedPipBounds.left;
+ loc[1] = animatedPipBounds.top;
+ }
+ };
+ mMagnetizedPip.setFlingToTargetEnabled(false);
+ }
+
+ return mMagnetizedPip;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
new file mode 100644
index 000000000000..cc8e3e0934e6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -0,0 +1,1081 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip2.phone.PhonePipMenuController.MENU_STATE_FULL;
+import static com.android.wm.shell.pip2.phone.PhonePipMenuController.MENU_STATE_NONE;
+import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.provider.DeviceConfig;
+import android.util.Size;
+import android.view.DisplayCutout;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
+import com.android.wm.shell.common.pip.PipPerfHintController;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.io.PrintWriter;
+import java.util.Optional;
+
+/**
+ * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
+ * the PIP.
+ */
+public class PipTouchHandler {
+
+ private static final String TAG = "PipTouchHandler";
+ private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
+
+ // Allow PIP to resize to a slightly bigger state upon touch
+ private boolean mEnableResize;
+ private final Context mContext;
+ private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+ @NonNull private final PipBoundsState mPipBoundsState;
+ @NonNull private final SizeSpecSource mSizeSpecSource;
+ private final PipUiEventLogger mPipUiEventLogger;
+ private final PipDismissTargetHandler mPipDismissTargetHandler;
+ private final ShellExecutor mMainExecutor;
+ @Nullable private final PipPerfHintController mPipPerfHintController;
+
+ private PipResizeGestureHandler mPipResizeGestureHandler;
+
+ private final PhonePipMenuController mMenuController;
+ private final AccessibilityManager mAccessibilityManager;
+
+ /**
+ * Whether PIP stash is enabled or not. When enabled, if the user flings toward the edge of the
+ * screen, it will be shown in "stashed" mode, where PIP will only show partially.
+ */
+ private boolean mEnableStash = true;
+
+ private float mStashVelocityThreshold;
+
+ // The reference inset bounds, used to determine the dismiss fraction
+ private final Rect mInsetBounds = new Rect();
+
+ // Used to workaround an issue where the WM rotation happens before we are notified, allowing
+ // us to send stale bounds
+ private int mDeferResizeToNormalBoundsUntilRotation = -1;
+ private int mDisplayRotation;
+
+ // Behaviour states
+ private int mMenuState = MENU_STATE_NONE;
+ private boolean mIsImeShowing;
+ private int mImeHeight;
+ private int mImeOffset;
+ private boolean mIsShelfShowing;
+ private int mShelfHeight;
+ private int mMovementBoundsExtraOffsets;
+ private int mBottomOffsetBufferPx;
+ private float mSavedSnapFraction = -1f;
+ private boolean mSendingHoverAccessibilityEvents;
+ private boolean mMovementWithinDismiss;
+
+ // Touch state
+ private final PipTouchState mTouchState;
+ private final FloatingContentCoordinator mFloatingContentCoordinator;
+ private PipMotionHelper mMotionHelper;
+ private PipTouchGesture mGesture;
+
+ // Temp vars
+ private final Rect mTmpBounds = new Rect();
+
+ /**
+ * A listener for the PIP menu activity.
+ */
+ private class PipMenuListener implements PhonePipMenuController.Listener {
+ @Override
+ public void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
+ PipTouchHandler.this.onPipMenuStateChangeStart(menuState, resize, callback);
+ }
+
+ @Override
+ public void onPipMenuStateChangeFinish(int menuState) {
+ setMenuState(menuState);
+ }
+
+ @Override
+ public void onPipExpand() {
+ mMotionHelper.expandLeavePip(false /* skipAnimation */);
+ }
+
+ @Override
+ public void onPipDismiss() {
+ mTouchState.removeDoubleTapTimeoutCallback();
+ mMotionHelper.dismissPip();
+ }
+
+ @Override
+ public void onPipShowMenu() {
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle());
+ }
+ }
+
+ @SuppressLint("InflateParams")
+ public PipTouchHandler(Context context,
+ ShellInit shellInit,
+ PhonePipMenuController menuController,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ @NonNull PipBoundsState pipBoundsState,
+ @NonNull SizeSpecSource sizeSpecSource,
+ PipMotionHelper pipMotionHelper,
+ FloatingContentCoordinator floatingContentCoordinator,
+ PipUiEventLogger pipUiEventLogger,
+ ShellExecutor mainExecutor,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+ mContext = context;
+ mMainExecutor = mainExecutor;
+ mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
+ mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mPipBoundsState = pipBoundsState;
+ mSizeSpecSource = sizeSpecSource;
+ mMenuController = menuController;
+ mPipUiEventLogger = pipUiEventLogger;
+ mFloatingContentCoordinator = floatingContentCoordinator;
+ mMenuController.addListener(new PipMenuListener());
+ mGesture = new DefaultPipTouchGesture();
+ mMotionHelper = pipMotionHelper;
+ mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
+ mMotionHelper, mainExecutor);
+ mTouchState = new PipTouchState(ViewConfiguration.get(context),
+ () -> {
+ if (mPipBoundsState.isStashed()) {
+ animateToUnStashedState();
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ } else {
+ mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
+ mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
+ willResizeMenu(),
+ shouldShowResizeHandle());
+ }
+ },
+ menuController::hideMenu,
+ mainExecutor);
+ mPipResizeGestureHandler =
+ new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
+ mTouchState, this::updateMovementBounds, pipUiEventLogger,
+ menuController, mainExecutor, mPipPerfHintController);
+
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ /**
+ * Called when the touch handler is initialized.
+ */
+ public void onInit() {
+ Resources res = mContext.getResources();
+ mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
+ reloadResources();
+
+ mMotionHelper.init();
+ mPipResizeGestureHandler.init();
+ mPipDismissTargetHandler.init();
+
+ mEnableStash = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ PIP_STASHING,
+ /* defaultValue = */ true);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ mMainExecutor,
+ properties -> {
+ if (properties.getKeyset().contains(PIP_STASHING)) {
+ mEnableStash = properties.getBoolean(
+ PIP_STASHING, /* defaultValue = */ true);
+ }
+ });
+ mStashVelocityThreshold = DeviceConfig.getFloat(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+ DEFAULT_STASH_VELOCITY_THRESHOLD);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ mMainExecutor,
+ properties -> {
+ if (properties.getKeyset().contains(PIP_STASH_MINIMUM_VELOCITY_THRESHOLD)) {
+ mStashVelocityThreshold = properties.getFloat(
+ PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+ DEFAULT_STASH_VELOCITY_THRESHOLD);
+ }
+ });
+ }
+
+ public PipTransitionController getTransitionHandler() {
+ // return mPipTaskOrganizer.getTransitionController();
+ return null;
+ }
+
+ private void reloadResources() {
+ final Resources res = mContext.getResources();
+ mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer);
+ mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
+ mPipDismissTargetHandler.updateMagneticTargetSize();
+ }
+
+ void onOverlayChanged() {
+ // onOverlayChanged is triggered upon theme change, update the dismiss target accordingly.
+ mPipDismissTargetHandler.init();
+ }
+
+ private boolean shouldShowResizeHandle() {
+ return false;
+ }
+
+ void setTouchGesture(PipTouchGesture gesture) {
+ mGesture = gesture;
+ }
+
+ void setTouchEnabled(boolean enabled) {
+ mTouchState.setAllowTouches(enabled);
+ }
+
+ void showPictureInPictureMenu() {
+ // Only show the menu if the user isn't currently interacting with the PiP
+ if (!mTouchState.isUserInteracting()) {
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ false /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
+ }
+ }
+
+ void onActivityPinned() {
+ mPipDismissTargetHandler.createOrUpdateDismissTarget();
+
+ mPipResizeGestureHandler.onActivityPinned();
+ mFloatingContentCoordinator.onContentAdded(mMotionHelper);
+ }
+
+ void onActivityUnpinned(ComponentName topPipActivity) {
+ if (topPipActivity == null) {
+ // Clean up state after the last PiP activity is removed
+ mPipDismissTargetHandler.cleanUpDismissTarget();
+
+ mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
+ }
+ mPipResizeGestureHandler.onActivityUnpinned();
+ }
+
+ void onPinnedStackAnimationEnded(
+ @PipAnimationController.TransitionDirection int direction) {
+ // Always synchronize the motion helper bounds once PiP animations finish
+ mMotionHelper.synchronizePinnedStackBounds();
+ updateMovementBounds();
+ if (direction == TRANSITION_DIRECTION_TO_PIP) {
+ // Set the initial bounds as the user resize bounds.
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+ }
+ }
+
+ void onConfigurationChanged() {
+ mPipResizeGestureHandler.onConfigurationChanged();
+ mMotionHelper.synchronizePinnedStackBounds();
+ reloadResources();
+
+ /*
+ if (mPipTaskOrganizer.isInPip()) {
+ // Recreate the dismiss target for the new orientation.
+ mPipDismissTargetHandler.createOrUpdateDismissTarget();
+ }
+ */
+ }
+
+ void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+ mIsImeShowing = imeVisible;
+ mImeHeight = imeHeight;
+ }
+
+ void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+ mIsShelfShowing = shelfVisible;
+ mShelfHeight = shelfHeight;
+ }
+
+ /**
+ * Called when SysUI state changed.
+ *
+ * @param isSysUiStateValid Is SysUI valid or not.
+ */
+ public void onSystemUiStateChanged(boolean isSysUiStateValid) {
+ mPipResizeGestureHandler.onSystemUiStateChanged(isSysUiStateValid);
+ }
+
+ void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) {
+ final Rect toMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(outBounds, insetBounds, toMovementBounds, 0);
+ final int prevBottom = mPipBoundsState.getMovementBounds().bottom
+ - mMovementBoundsExtraOffsets;
+ if ((prevBottom - mBottomOffsetBufferPx) <= curBounds.top) {
+ outBounds.offsetTo(outBounds.left, toMovementBounds.bottom);
+ }
+ }
+
+ /**
+ * Responds to IPinnedStackListener on resetting aspect ratio for the pinned window.
+ */
+ public void onAspectRatioChanged() {
+ mPipResizeGestureHandler.invalidateUserResizeBounds();
+ }
+
+ void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds,
+ boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) {
+ // Set the user resized bounds equal to the new normal bounds in case they were
+ // invalidated (e.g. by an aspect ratio change).
+ if (mPipResizeGestureHandler.getUserResizeBounds().isEmpty()) {
+ mPipResizeGestureHandler.setUserResizeBounds(normalBounds);
+ }
+
+ final int bottomOffset = mIsImeShowing ? mImeHeight : 0;
+ final boolean fromDisplayRotationChanged = (mDisplayRotation != displayRotation);
+ if (fromDisplayRotationChanged) {
+ mTouchState.reset();
+ }
+
+ // Re-calculate the expanded bounds
+ Rect normalMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(normalBounds, insetBounds,
+ normalMovementBounds, bottomOffset);
+
+ if (mPipBoundsState.getMovementBounds().isEmpty()) {
+ // mMovementBounds is not initialized yet and a clean movement bounds without
+ // bottom offset shall be used later in this function.
+ mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
+ mPipBoundsState.getMovementBounds(), 0 /* bottomOffset */);
+ }
+
+ // Calculate the expanded size
+ float aspectRatio = (float) normalBounds.width() / normalBounds.height();
+ Size expandedSize = mSizeSpecSource.getDefaultSize(aspectRatio);
+ mPipBoundsState.setExpandedBounds(
+ new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));
+ Rect expandedMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(
+ mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds,
+ bottomOffset);
+
+ updatePipSizeConstraints(normalBounds, aspectRatio);
+
+ // The extra offset does not really affect the movement bounds, but are applied based on the
+ // current state (ime showing, or shelf offset) when we need to actually shift
+ int extraOffset = Math.max(
+ mIsImeShowing ? mImeOffset : 0,
+ !mIsImeShowing && mIsShelfShowing ? mShelfHeight : 0);
+
+ // Update the movement bounds after doing the calculations based on the old movement bounds
+ // above
+ mPipBoundsState.setNormalMovementBounds(normalMovementBounds);
+ mPipBoundsState.setExpandedMovementBounds(expandedMovementBounds);
+ mDisplayRotation = displayRotation;
+ mInsetBounds.set(insetBounds);
+ updateMovementBounds();
+ mMovementBoundsExtraOffsets = extraOffset;
+
+ // If we have a deferred resize, apply it now
+ if (mDeferResizeToNormalBoundsUntilRotation == displayRotation) {
+ mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction,
+ mPipBoundsState.getNormalMovementBounds(), mPipBoundsState.getMovementBounds(),
+ true /* immediate */);
+ mSavedSnapFraction = -1f;
+ mDeferResizeToNormalBoundsUntilRotation = -1;
+ }
+ }
+
+ /**
+ * Update the values for min/max allowed size of picture in picture window based on the aspect
+ * ratio.
+ * @param aspectRatio aspect ratio to use for the calculation of min/max size
+ */
+ public void updateMinMaxSize(float aspectRatio) {
+ updatePipSizeConstraints(mPipBoundsState.getNormalBounds(),
+ aspectRatio);
+ }
+
+ private void updatePipSizeConstraints(Rect normalBounds,
+ float aspectRatio) {
+ if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
+ updatePinchResizeSizeConstraints(aspectRatio);
+ } else {
+ mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height());
+ mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(),
+ mPipBoundsState.getExpandedBounds().height());
+ }
+ }
+
+ private void updatePinchResizeSizeConstraints(float aspectRatio) {
+ mPipBoundsState.updateMinMaxSize(aspectRatio);
+ mPipResizeGestureHandler.updateMinSize(mPipBoundsState.getMinSize().x,
+ mPipBoundsState.getMinSize().y);
+ mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getMaxSize().x,
+ mPipBoundsState.getMaxSize().y);
+ }
+
+ /**
+ * TODO Add appropriate description
+ */
+ public void onRegistrationChanged(boolean isRegistered) {
+ if (isRegistered) {
+ // Register the accessibility connection.
+ } else {
+ mAccessibilityManager.setPictureInPictureActionReplacingConnection(null);
+ }
+ if (!isRegistered && mTouchState.isUserInteracting()) {
+ // If the input consumer is unregistered while the user is interacting, then we may not
+ // get the final TOUCH_UP event, so clean up the dismiss target as well
+ mPipDismissTargetHandler.cleanUpDismissTarget();
+ }
+ }
+
+ private void onAccessibilityShowMenu() {
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
+ }
+
+ /**
+ * TODO Add appropriate description
+ */
+ public boolean handleTouchEvent(InputEvent inputEvent) {
+ // Skip any non motion events
+ if (!(inputEvent instanceof MotionEvent)) {
+ return true;
+ }
+
+ // do not process input event if not allowed
+ if (!mTouchState.getAllowInputEvents()) {
+ return true;
+ }
+
+ MotionEvent ev = (MotionEvent) inputEvent;
+ if (!mPipBoundsState.isStashed() && mPipResizeGestureHandler.willStartResizeGesture(ev)) {
+ // Initialize the touch state for the gesture, but immediately reset to invalidate the
+ // gesture
+ mTouchState.onTouchEvent(ev);
+ mTouchState.reset();
+ return true;
+ }
+
+ if (mPipResizeGestureHandler.hasOngoingGesture()) {
+ mGesture.cleanUpHighPerfSessionMaybe();
+ mPipDismissTargetHandler.hideDismissTargetMaybe();
+ return true;
+ }
+
+ if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting())
+ && mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) {
+ // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
+ // to the touch state. Touch state needs a DOWN event in order to later process MOVE
+ // events it'll receive if the object is dragged out of the magnetic field.
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mTouchState.onTouchEvent(ev);
+ }
+
+ // Continue tracking velocity when the object is in the magnetic field, since we want to
+ // respect touch input velocity if the object is dragged out and then flung.
+ mTouchState.addMovementToVelocityTracker(ev);
+
+ return true;
+ }
+
+ if (!mTouchState.isUserInteracting()) {
+ ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Waiting to start the entry animation, skip the motion event.", TAG);
+ return true;
+ }
+
+ // Update the touch state
+ mTouchState.onTouchEvent(ev);
+
+ boolean shouldDeliverToMenu = mMenuState != MENU_STATE_NONE;
+
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN: {
+ mGesture.onDown(mTouchState);
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ if (mGesture.onMove(mTouchState)) {
+ break;
+ }
+
+ shouldDeliverToMenu = !mTouchState.isDragging();
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ // Update the movement bounds again if the state has changed since the user started
+ // dragging (ie. when the IME shows)
+ updateMovementBounds();
+
+ if (mGesture.onUp(mTouchState)) {
+ break;
+ }
+ }
+ // Fall through to clean up
+ case MotionEvent.ACTION_CANCEL: {
+ shouldDeliverToMenu = !mTouchState.startedDragging() && !mTouchState.isDragging();
+ mTouchState.reset();
+ break;
+ }
+ case MotionEvent.ACTION_HOVER_ENTER: {
+ // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
+ // on and changing MotionEvents into HoverEvents.
+ // Let's not enable menu show/hide for a11y services.
+ if (!mAccessibilityManager.isTouchExplorationEnabled()) {
+ mTouchState.removeHoverExitTimeoutCallback();
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ false /* allowMenuTimeout */, false /* willResizeMenu */,
+ shouldShowResizeHandle());
+ }
+ }
+ // Fall through
+ case MotionEvent.ACTION_HOVER_MOVE: {
+ if (!shouldDeliverToMenu && !mSendingHoverAccessibilityEvents) {
+ sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ mSendingHoverAccessibilityEvents = true;
+ }
+ break;
+ }
+ case MotionEvent.ACTION_HOVER_EXIT: {
+ // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
+ // on and changing MotionEvents into HoverEvents.
+ // Let's not enable menu show/hide for a11y services.
+ if (!mAccessibilityManager.isTouchExplorationEnabled()) {
+ mTouchState.scheduleHoverExitTimeoutCallback();
+ }
+ if (!shouldDeliverToMenu && mSendingHoverAccessibilityEvents) {
+ sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ mSendingHoverAccessibilityEvents = false;
+ }
+ break;
+ }
+ }
+
+ shouldDeliverToMenu &= !mPipBoundsState.isStashed();
+
+ // Deliver the event to PipMenuActivity to handle button click if the menu has shown.
+ if (shouldDeliverToMenu) {
+ final MotionEvent cloneEvent = MotionEvent.obtain(ev);
+ // Send the cancel event and cancel menu timeout if it starts to drag.
+ if (mTouchState.startedDragging()) {
+ cloneEvent.setAction(MotionEvent.ACTION_CANCEL);
+ mMenuController.pokeMenu();
+ }
+
+ mMenuController.handlePointerEvent(cloneEvent);
+ cloneEvent.recycle();
+ }
+
+ return true;
+ }
+
+ private void sendAccessibilityHoverEvent(int type) {
+ if (!mAccessibilityManager.isEnabled()) {
+ return;
+ }
+
+ AccessibilityEvent event = AccessibilityEvent.obtain(type);
+ event.setImportantForAccessibility(true);
+ event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID);
+ event.setWindowId(
+ AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+
+ /**
+ * Called when the PiP menu state is in the process of animating/changing from one to another.
+ */
+ private void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
+ if (mMenuState == menuState && !resize) {
+ return;
+ }
+
+ if (menuState == MENU_STATE_FULL && mMenuState != MENU_STATE_FULL) {
+ // Save the current snap fraction and if we do not drag or move the PiP, then
+ // we store back to this snap fraction. Otherwise, we'll reset the snap
+ // fraction and snap to the closest edge.
+ if (resize) {
+ // PIP is too small to show the menu actions and thus needs to be resized to a
+ // size that can fit them all. Resize to the default size.
+ animateToNormalSize(callback);
+ }
+ } else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) {
+ // Try and restore the PiP to the closest edge, using the saved snap fraction
+ // if possible
+ if (resize && !mPipResizeGestureHandler.isResizing()) {
+ if (mDeferResizeToNormalBoundsUntilRotation == -1) {
+ // This is a very special case: when the menu is expanded and visible,
+ // navigating to another activity can trigger auto-enter PiP, and if the
+ // revealed activity has a forced rotation set, then the controller will get
+ // updated with the new rotation of the display. However, at the same time,
+ // SystemUI will try to hide the menu by creating an animation to the normal
+ // bounds which are now stale. In such a case we defer the animation to the
+ // normal bounds until after the next onMovementBoundsChanged() call to get the
+ // bounds in the new orientation
+ int displayRotation = mContext.getDisplay().getRotation();
+ if (mDisplayRotation != displayRotation) {
+ mDeferResizeToNormalBoundsUntilRotation = displayRotation;
+ }
+ }
+
+ if (mDeferResizeToNormalBoundsUntilRotation == -1) {
+ animateToUnexpandedState(getUserResizeBounds());
+ }
+ } else {
+ mSavedSnapFraction = -1f;
+ }
+ }
+ }
+
+ private void setMenuState(int menuState) {
+ mMenuState = menuState;
+ updateMovementBounds();
+ // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip
+ // as well, or it can't handle a11y focus and pip menu can't perform any action.
+ onRegistrationChanged(menuState == MENU_STATE_NONE);
+ if (menuState == MENU_STATE_NONE) {
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_HIDE_MENU);
+ } else if (menuState == MENU_STATE_FULL) {
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_SHOW_MENU);
+ }
+ }
+
+ private void animateToMaximizedState(Runnable callback) {
+ Rect maxMovementBounds = new Rect();
+ Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x,
+ mPipBoundsState.getMaxSize().y);
+ mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, maxMovementBounds,
+ mIsImeShowing ? mImeHeight : 0);
+ mSavedSnapFraction = mMotionHelper.animateToExpandedState(maxBounds,
+ mPipBoundsState.getMovementBounds(), maxMovementBounds,
+ callback);
+ }
+
+ private void animateToNormalSize(Runnable callback) {
+ // Save the current bounds as the user-resize bounds.
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+
+ final Size minMenuSize = mMenuController.getEstimatedMinMenuSize();
+ final Rect normalBounds = mPipBoundsState.getNormalBounds();
+ final Rect destBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds,
+ minMenuSize);
+ Rect restoredMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(destBounds,
+ mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
+ mSavedSnapFraction = mMotionHelper.animateToExpandedState(destBounds,
+ mPipBoundsState.getMovementBounds(), restoredMovementBounds, callback);
+ }
+
+ private void animateToUnexpandedState(Rect restoreBounds) {
+ Rect restoredMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(restoreBounds,
+ mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
+ mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
+ restoredMovementBounds, mPipBoundsState.getMovementBounds(), false /* immediate */);
+ mSavedSnapFraction = -1f;
+ }
+
+ private void animateToUnStashedState() {
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ final boolean onLeftEdge = pipBounds.left < mPipBoundsState.getDisplayBounds().left;
+ final Rect unStashedBounds = new Rect(0, pipBounds.top, 0, pipBounds.bottom);
+ unStashedBounds.left = onLeftEdge ? mInsetBounds.left
+ : mInsetBounds.right - pipBounds.width();
+ unStashedBounds.right = onLeftEdge ? mInsetBounds.left + pipBounds.width()
+ : mInsetBounds.right;
+ mMotionHelper.animateToUnStashedBounds(unStashedBounds);
+ }
+
+ /**
+ * @return the motion helper.
+ */
+ public PipMotionHelper getMotionHelper() {
+ return mMotionHelper;
+ }
+
+ @VisibleForTesting
+ public PipResizeGestureHandler getPipResizeGestureHandler() {
+ return mPipResizeGestureHandler;
+ }
+
+ @VisibleForTesting
+ public void setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler) {
+ mPipResizeGestureHandler = pipResizeGestureHandler;
+ }
+
+ @VisibleForTesting
+ public void setPipMotionHelper(PipMotionHelper pipMotionHelper) {
+ mMotionHelper = pipMotionHelper;
+ }
+
+ Rect getUserResizeBounds() {
+ return mPipResizeGestureHandler.getUserResizeBounds();
+ }
+
+ /**
+ * Sets the user resize bounds tracked by {@link PipResizeGestureHandler}
+ */
+ void setUserResizeBounds(Rect bounds) {
+ mPipResizeGestureHandler.setUserResizeBounds(bounds);
+ }
+
+ /**
+ * Gesture controlling normal movement of the PIP.
+ */
+ private class DefaultPipTouchGesture extends PipTouchGesture {
+ private final Point mStartPosition = new Point();
+ private final PointF mDelta = new PointF();
+ private boolean mShouldHideMenuAfterFling;
+
+ @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
+ private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
+
+ @Override
+ public void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
+ }
+ }
+
+ @Override
+ public void onDown(PipTouchState touchState) {
+ if (!touchState.isUserInteracting()) {
+ return;
+ }
+
+ if (mPipPerfHintController != null) {
+ // Cache the PiP high perf session to close it upon touch up.
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "DefaultPipTouchGesture#onDown");
+ }
+
+ Rect bounds = getPossiblyMotionBounds();
+ mDelta.set(0f, 0f);
+ mStartPosition.set(bounds.left, bounds.top);
+ mMovementWithinDismiss = touchState.getDownTouchPosition().y
+ >= mPipBoundsState.getMovementBounds().bottom;
+ mMotionHelper.setSpringingToTouch(false);
+ // mPipDismissTargetHandler.setTaskLeash(mPipTaskOrganizer.getSurfaceControl());
+
+ // If the menu is still visible then just poke the menu
+ // so that it will timeout after the user stops touching it
+ if (mMenuState != MENU_STATE_NONE && !mPipBoundsState.isStashed()) {
+ mMenuController.pokeMenu();
+ }
+ }
+
+ @Override
+ public boolean onMove(PipTouchState touchState) {
+ if (!touchState.isUserInteracting()) {
+ return false;
+ }
+
+ if (touchState.startedDragging()) {
+ mSavedSnapFraction = -1f;
+ mPipDismissTargetHandler.showDismissTargetMaybe();
+ }
+
+ if (touchState.isDragging()) {
+ mPipBoundsState.setHasUserMovedPip(true);
+
+ // Move the pinned stack freely
+ final PointF lastDelta = touchState.getLastTouchDelta();
+ float lastX = mStartPosition.x + mDelta.x;
+ float lastY = mStartPosition.y + mDelta.y;
+ float left = lastX + lastDelta.x;
+ float top = lastY + lastDelta.y;
+
+ // Add to the cumulative delta after bounding the position
+ mDelta.x += left - lastX;
+ mDelta.y += top - lastY;
+
+ mTmpBounds.set(getPossiblyMotionBounds());
+ mTmpBounds.offsetTo((int) left, (int) top);
+ mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
+
+ final PointF curPos = touchState.getLastTouchPosition();
+ if (mMovementWithinDismiss) {
+ // Track if movement remains near the bottom edge to identify swipe to dismiss
+ mMovementWithinDismiss = curPos.y >= mPipBoundsState.getMovementBounds().bottom;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onUp(PipTouchState touchState) {
+ mPipDismissTargetHandler.hideDismissTargetMaybe();
+ mPipDismissTargetHandler.setTaskLeash(null);
+
+ if (!touchState.isUserInteracting()) {
+ return false;
+ }
+
+ final PointF vel = touchState.getVelocity();
+
+ if (touchState.isDragging()) {
+ if (mMenuState != MENU_STATE_NONE) {
+ // If the menu is still visible, then just poke the menu so that
+ // it will timeout after the user stops touching it
+ mMenuController.showMenu(mMenuState, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
+ }
+ mShouldHideMenuAfterFling = mMenuState == MENU_STATE_NONE;
+
+ // Reset the touch state on up before the fling settles
+ mTouchState.reset();
+ if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) {
+ mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */);
+ } else {
+ if (mPipBoundsState.isStashed()) {
+ // Reset stashed state if previously stashed
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ }
+ mMotionHelper.flingToSnapTarget(vel.x, vel.y,
+ this::flingEndAction /* endAction */);
+ }
+ } else if (mTouchState.isDoubleTap() && !mPipBoundsState.isStashed()
+ && mMenuState != MENU_STATE_FULL) {
+ // If using pinch to zoom, double-tap functions as resizing between max/min size
+ if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
+ final boolean toExpand = mPipBoundsState.getBounds().width()
+ < mPipBoundsState.getMaxSize().x
+ && mPipBoundsState.getBounds().height()
+ < mPipBoundsState.getMaxSize().y;
+ if (mMenuController.isMenuVisible()) {
+ mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
+ }
+
+ // the size to toggle to after a double tap
+ int nextSize = PipDoubleTapHelper
+ .nextSizeSpec(mPipBoundsState, getUserResizeBounds());
+
+ // actually toggle to the size chosen
+ if (nextSize == PipDoubleTapHelper.SIZE_SPEC_MAX) {
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+ animateToMaximizedState(null);
+ } else if (nextSize == PipDoubleTapHelper.SIZE_SPEC_DEFAULT) {
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+ animateToNormalSize(null);
+ } else {
+ animateToUnexpandedState(getUserResizeBounds());
+ }
+ } else {
+ // Expand to fullscreen if this is a double tap
+ // the PiP should be frozen until the transition ends
+ setTouchEnabled(false);
+ mMotionHelper.expandLeavePip(false /* skipAnimation */);
+ }
+ } else if (mMenuState != MENU_STATE_FULL) {
+ if (mPipBoundsState.isStashed()) {
+ // Unstash immediately if stashed, and don't wait for the double tap timeout
+ animateToUnStashedState();
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ mTouchState.removeDoubleTapTimeoutCallback();
+ } else if (!mTouchState.isWaitingForDoubleTap()) {
+ // User has stalled long enough for this not to be a drag or a double tap,
+ // just expand the menu
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
+ } else {
+ // Next touch event _may_ be the second tap for the double-tap, schedule a
+ // fallback runnable to trigger the menu if no touch event occurs before the
+ // next tap
+ mTouchState.scheduleDoubleTapTimeoutCallback();
+ }
+ }
+ cleanUpHighPerfSessionMaybe();
+ return true;
+ }
+
+ private void stashEndAction() {
+ if (mPipBoundsState.getBounds().left < 0
+ && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) {
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_LEFT);
+ mPipBoundsState.setStashed(STASH_TYPE_LEFT);
+ } else if (mPipBoundsState.getBounds().left >= 0
+ && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) {
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_RIGHT);
+ mPipBoundsState.setStashed(STASH_TYPE_RIGHT);
+ }
+ mMenuController.hideMenu();
+ }
+
+ private void flingEndAction() {
+ if (mShouldHideMenuAfterFling) {
+ // If the menu is not visible, then we can still be showing the activity for the
+ // dismiss overlay, so just finish it after the animation completes
+ mMenuController.hideMenu();
+ }
+ }
+
+ private boolean shouldStash(PointF vel, Rect motionBounds) {
+ final boolean flingToLeft = vel.x < -mStashVelocityThreshold;
+ final boolean flingToRight = vel.x > mStashVelocityThreshold;
+ final int offset = motionBounds.width() / 2;
+ final boolean droppingOnLeft =
+ motionBounds.left < mPipBoundsState.getDisplayBounds().left - offset;
+ final boolean droppingOnRight =
+ motionBounds.right > mPipBoundsState.getDisplayBounds().right + offset;
+
+ // Do not allow stash if the destination edge contains display cutout. We only
+ // compare the left and right edges since we do not allow stash on top / bottom.
+ final DisplayCutout displayCutout =
+ mPipBoundsState.getDisplayLayout().getDisplayCutout();
+ if (displayCutout != null) {
+ if ((flingToLeft || droppingOnLeft)
+ && !displayCutout.getBoundingRectLeft().isEmpty()) {
+ return false;
+ } else if ((flingToRight || droppingOnRight)
+ && !displayCutout.getBoundingRectRight().isEmpty()) {
+ return false;
+ }
+ }
+
+ // If user flings the PIP window above the minimum velocity, stash PIP.
+ // Only allow stashing to the edge if PIP wasn't previously stashed on the opposite
+ // edge.
+ final boolean stashFromFlingToEdge =
+ (flingToLeft && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT)
+ || (flingToRight && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT);
+
+ // If User releases the PIP window while it's out of the display bounds, put
+ // PIP into stashed mode.
+ final boolean stashFromDroppingOnEdge = droppingOnLeft || droppingOnRight;
+
+ return stashFromFlingToEdge || stashFromDroppingOnEdge;
+ }
+ }
+
+ /**
+ * Updates the current movement bounds based on whether the menu is currently visible and
+ * resized.
+ */
+ private void updateMovementBounds() {
+ mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
+ mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
+ mMotionHelper.onMovementBoundsChanged();
+ }
+
+ private Rect getMovementBounds(Rect curBounds) {
+ Rect movementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(curBounds, mInsetBounds,
+ movementBounds, mIsImeShowing ? mImeHeight : 0);
+ return movementBounds;
+ }
+
+ /**
+ * @return {@code true} if the menu should be resized on tap because app explicitly specifies
+ * PiP window size that is too small to hold all the actions.
+ */
+ private boolean willResizeMenu() {
+ if (!mEnableResize) {
+ return false;
+ }
+ final Size estimatedMinMenuSize = mMenuController.getEstimatedMinMenuSize();
+ if (estimatedMinMenuSize == null) {
+ ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to get estimated menu size", TAG);
+ return false;
+ }
+ final Rect currentBounds = mPipBoundsState.getBounds();
+ return currentBounds.width() < estimatedMinMenuSize.getWidth()
+ || currentBounds.height() < estimatedMinMenuSize.getHeight();
+ }
+
+ /**
+ * Returns the PIP bounds if we're not in the middle of a motion operation, or the current,
+ * temporary motion bounds otherwise.
+ */
+ Rect getPossiblyMotionBounds() {
+ return mPipBoundsState.getMotionBoundsState().isInMotion()
+ ? mPipBoundsState.getMotionBoundsState().getBoundsInMotion()
+ : mPipBoundsState.getBounds();
+ }
+
+ void setOhmOffset(int offset) {
+ mPipResizeGestureHandler.setOhmOffset(offset);
+ }
+
+ /**
+ * Dumps the {@link PipTouchHandler} state.
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mMenuState=" + mMenuState);
+ pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
+ pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
+ pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
+ pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
+ pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction);
+ pw.println(innerPrefix + "mMovementBoundsExtraOffsets=" + mMovementBoundsExtraOffsets);
+ mPipBoundsAlgorithm.dump(pw, innerPrefix);
+ mTouchState.dump(pw, innerPrefix);
+ if (mPipResizeGestureHandler != null) {
+ mPipResizeGestureHandler.dump(pw, innerPrefix);
+ }
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 576219769e61..6aad4e2c9da4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -18,9 +18,14 @@ package com.android.wm.shell.splitscreen;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.graphics.Rect;
+import android.os.Bundle;
+import android.window.RemoteTransition;
+import com.android.internal.logging.InstanceId;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.shared.annotations.ExternalThread;
@@ -72,6 +77,12 @@ public interface SplitScreen {
}
}
+ /** Launches a pair of tasks into splitscreen */
+ void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId);
+
/** Registers listener that gets split screen callback. */
void registerSplitScreenListener(@NonNull SplitScreenListener listener,
@NonNull Executor executor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 088bb4814491..547457b018a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -506,6 +506,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mStageCoordinator.getActivateSplitPosition(taskInfo);
}
+ /** Start two tasks in parallel as a splitscreen pair. */
+ public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ @PersistentSnapPosition int snapPosition,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ mStageCoordinator.startTasks(taskId1, options1, taskId2, options2, splitPosition,
+ snapPosition, remoteTransition, instanceId);
+ }
+
/**
* Move a task to split select
* @param taskInfo the task being moved to split select
@@ -1120,6 +1129,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
};
@Override
+ public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
+ @Nullable Bundle options2, int splitPosition, int snapPosition,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ mMainExecutor.execute(() -> SplitScreenController.this.startTasks(
+ taskId1, options1, taskId2, options2, splitPosition, snapPosition,
+ remoteTransition, instanceId));
+ }
+
+ @Override
public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
if (mExecutors.containsKey(listener)) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 74e85f8dd468..9adb67c8a65e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -507,6 +507,15 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final Point animRelOffset = new Point(
change.getEndAbsBounds().left - animRoot.getOffset().x,
change.getEndAbsBounds().top - animRoot.getOffset().y);
+
+ if (change.getActivityComponent() != null) {
+ // For appcompat letterbox: we intentionally report the task-bounds so that we
+ // can animate as-if letterboxes are "part of" the activity. This means we can't
+ // always rely solely on endAbsBounds and need to also max with endRelOffset.
+ animRelOffset.x = Math.max(animRelOffset.x, change.getEndRelOffset().x);
+ animRelOffset.y = Math.max(animRelOffset.y, change.getEndRelOffset().y);
+ }
+
if (change.getActivityComponent() != null && !isActivityLevel) {
// At this point, this is an independent activity change in a non-activity
// transition. This means that an activity transition got erroneously combined
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index c26604a84a61..7c2ba455c0c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -293,7 +293,13 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
@Override
public void onFoldStateChanged(boolean isFolded) {
if (isFolded) {
+ // Reset unfold animation finished flag on folding, so it could be used next time
+ // when we unfold the device as an indication that animation hasn't finished yet
mAnimationFinished = false;
+
+ // If we are currently animating unfold animation we should finish it because
+ // the animation might not start and finish as the device was folded
+ finishTransitionIfNeeded();
}
}
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 d0879434657d..963b1303c379 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
@@ -19,6 +19,7 @@ package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.windowingModeToString;
+import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
@@ -28,6 +29,7 @@ import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
@@ -39,6 +41,7 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.util.Log;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -433,15 +436,20 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
private void loadAppInfo() {
+ final ActivityInfo activityInfo = mTaskInfo.topActivityInfo;
+ if (activityInfo == null) {
+ Log.e(TAG, "Top activity info not found in task");
+ return;
+ }
PackageManager pm = mContext.getApplicationContext().getPackageManager();
final IconProvider provider = new IconProvider(mContext);
- mAppIconDrawable = provider.getIcon(mTaskInfo.topActivityInfo);
+ mAppIconDrawable = provider.getIcon(activityInfo);
final Resources resources = mContext.getResources();
final BaseIconFactory factory = new BaseIconFactory(mContext,
resources.getDisplayMetrics().densityDpi,
resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT);
- final ApplicationInfo applicationInfo = mTaskInfo.topActivityInfo.applicationInfo;
+ final ApplicationInfo applicationInfo = activityInfo.applicationInfo;
mAppName = pm.getApplicationLabel(applicationInfo);
}
@@ -752,7 +760,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final int action = ev.getActionMasked();
// The comparison against ACTION_UP is needed for the cancel drag to desktop case.
handle.setHovered(inHandle && action != ACTION_UP);
- handle.setPressed(inHandle && action == ACTION_DOWN);
+ // We want handle to remain pressed if the pointer moves outside of it during a drag.
+ handle.setPressed((inHandle && action == ACTION_DOWN)
+ || (handle.isPressed() && action != ACTION_UP && action != ACTION_CANCEL));
if (isHandleMenuActive()) {
mHandleMenu.checkMotionEvent(ev);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt
new file mode 100644
index 000000000000..b21c3f522eab
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.ImageButton
+
+/**
+ * [ImageButton] for the handle at the top of fullscreen apps. Has custom hover
+ * and press handling to grow the handle on hover enter and shrink the handle on
+ * hover exit and press.
+ */
+class HandleImageButton (context: Context?, attrs: AttributeSet?) :
+ ImageButton(context, attrs) {
+ private val handleAnimator = ValueAnimator()
+
+ override fun onHoverChanged(hovered: Boolean) {
+ super.onHoverChanged(hovered)
+ if (hovered) {
+ animateHandle(HANDLE_HOVER_ANIM_DURATION, HANDLE_HOVER_ENTER_SCALE)
+ } else {
+ if (!isPressed) {
+ animateHandle(HANDLE_HOVER_ANIM_DURATION, HANDLE_DEFAULT_SCALE)
+ }
+ }
+ }
+
+ override fun setPressed(pressed: Boolean) {
+ if (isPressed != pressed) {
+ super.setPressed(pressed)
+ if (pressed) {
+ animateHandle(HANDLE_PRESS_ANIM_DURATION, HANDLE_PRESS_DOWN_SCALE)
+ } else {
+ animateHandle(HANDLE_PRESS_ANIM_DURATION, HANDLE_DEFAULT_SCALE)
+ }
+ }
+ }
+
+ private fun animateHandle(duration: Long, endScale: Float) {
+ if (handleAnimator.isRunning) {
+ handleAnimator.cancel()
+ }
+ handleAnimator.duration = duration
+ handleAnimator.setFloatValues(scaleX, endScale)
+ handleAnimator.addUpdateListener { animator ->
+ scaleX = animator.animatedValue as Float
+ }
+ handleAnimator.start()
+ }
+
+ companion object {
+ /** The duration of animations related to hover state. **/
+ private const val HANDLE_HOVER_ANIM_DURATION = 300L
+ /** The duration of animations related to pressed state. **/
+ private const val HANDLE_PRESS_ANIM_DURATION = 200L
+ /** Ending scale for hover enter. **/
+ private const val HANDLE_HOVER_ENTER_SCALE = 1.2f
+ /** Ending scale for press down. **/
+ private const val HANDLE_PRESS_DOWN_SCALE = 0.85f
+ /** Default scale for handle. **/
+ private const val HANDLE_DEFAULT_SCALE = 1f
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt
index 2fda3ea8daee..7898567b70e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt
@@ -21,7 +21,7 @@ import android.view.MotionEvent
import android.widget.ImageButton
/**
- * A custom [ImageButton] that intentionally does not handle hover events.
+ * A custom [ImageButton] for buttons inside handle menu that intentionally doesn't handle hovers.
* This is due to the hover events being handled by [DesktopModeWindowDecorViewModel]
* in order to take the status bar layer into account. Handling it in both classes results in a
* flicker when the hover moves from outside to inside status bar layer.
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index 3380adac0b3f..e9eabb4162e3 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.appcompat
import android.content.Context
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.FlickerTestData
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
import com.android.server.wm.flicker.helpers.LetterboxAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.BaseTest
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index f08eba5a73a3..16c2d47f9db3 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.appcompat
import android.platform.test.annotations.Postsubmit
import android.tools.flicker.assertions.FlickerTest
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.traces.component.ComponentNameMatcher
import androidx.test.filters.RequiresDevice
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
index 826fc541687e..d85b7718aa56 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -19,11 +19,11 @@ package com.android.wm.shell.flicker.appcompat
import android.platform.test.annotations.Postsubmit
import android.tools.Rotation
import android.tools.flicker.assertions.FlickerTest
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.traces.component.ComponentNameMatcher
import androidx.test.filters.RequiresDevice
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
index 26e78bf625ba..164534c14d28 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -16,17 +16,17 @@
package com.android.wm.shell.flicker.appcompat
+import android.graphics.Rect
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.RequiresDevice
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.datatypes.Rect
import android.tools.flicker.assertions.FlickerTest
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.traces.component.ComponentNameMatcher
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -260,7 +260,7 @@ class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : BaseAp
companion object {
/** {@inheritDoc} */
- private var startDisplayBounds = Rect.EMPTY
+ private var startDisplayBounds = Rect()
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
index 2aa84b4e55b8..034d54b185ed 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
@@ -53,7 +53,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
class RepositionFixedPortraitAppTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
- val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation).bounds
+ val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
index 7ffa23345589..22543aa9f773 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
@@ -16,19 +16,19 @@
package com.android.wm.shell.flicker.appcompat
+import android.graphics.Rect
import android.os.Build
import android.platform.test.annotations.Postsubmit
import android.system.helpers.CommandsHelper
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.datatypes.Rect
import android.tools.flicker.assertions.FlickerTest
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.helpers.FIND_TIMEOUT
+import android.tools.traces.component.ComponentNameMatcher
import android.tools.traces.parsers.toFlickerComponent
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
@@ -167,7 +167,7 @@ class RotateImmersiveAppInFullscreenTest(flicker: LegacyFlickerTest) : BaseAppCo
}
companion object {
- private var startDisplayBounds = Rect.EMPTY
+ private var startDisplayBounds = Rect()
const val LAUNCHER_PACKAGE = "com.google.android.apps.nexuslauncher"
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
index 521c0d0aaeb7..2a9b1078afe3 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
@@ -19,11 +19,11 @@ package com.android.wm.shell.flicker.bubble
import android.content.Context
import android.graphics.Point
import android.platform.test.annotations.Presubmit
-import android.tools.flicker.subject.layers.LayersTraceSubject
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.subject.layers.LayersTraceSubject
+import android.tools.traces.component.ComponentNameMatcher
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.test.uiautomator.By
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index e059ac78dc6b..9ef49c1c9e7e 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.bubble
import android.platform.test.annotations.Postsubmit
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
import android.view.WindowInsets
import android.view.WindowManager
import androidx.test.filters.FlakyTest
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
index a0edcfb17971..371fee225b34 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -66,9 +66,7 @@ class AutoEnterPipWithSourceRectHintTest(flicker: LegacyFlickerTest) :
@Test
fun pipOverlayNotShown() {
val overlay = ComponentNameMatcher.PIP_CONTENT_OVERLAY
- flicker.assertLayers {
- this.notContains(overlay)
- }
+ flicker.assertLayers { this.notContains(overlay) }
}
@Presubmit
@Test
@@ -83,4 +81,4 @@ class AutoEnterPipWithSourceRectHintTest(flicker: LegacyFlickerTest) :
// auto enter and sourceRectHint that causes the app to move outside of the display
// bounds during the transition.
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index 031acf4919eb..1c0820a2b0db 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -17,10 +17,10 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
import com.android.wm.shell.flicker.pip.common.ClosePipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -69,7 +69,8 @@ class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition
wmHelper.currentState.layerState
.getLayerWithBuffer(barComponent)
?.visibleRegion
- ?.height
+ ?.bounds
+ ?.height()
?: error("Couldn't find Nav or Task bar layer")
// The dismiss button doesn't appear at the complete bottom of the screen,
// it appears above the hot seat but `hotseatBarSize` is not available outside
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index 9a1bd267ea1f..270ebf5dd29b 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -21,12 +21,12 @@ import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.Rotation
import android.tools.flicker.assertions.FlickerTest
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.helpers.WindowUtils
+import android.tools.traces.component.ComponentNameMatcher
import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 25614ef63ccc..eeff167b1fc4 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.tools.Rotation
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.traces.component.ComponentNameMatcher
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
index 5f25d70acf7c..f81e8490b0eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -191,8 +191,9 @@ class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams() = LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index e184cf04e4ae..ad3c69eae06a 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -19,12 +19,12 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.tools.Rotation
import android.tools.flicker.assertions.FlickerTest
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.helpers.WindowUtils
+import android.tools.traces.component.ComponentNameMatcher
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.pip.common.PipTransition
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 68417066ac0a..16d08e5e9055 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.tools.Rotation
-import android.tools.flicker.subject.exceptions.IncorrectRegionException
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.subject.exceptions.IncorrectRegionException
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
index c9f4a6ca75b1..65b60ce1022b 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
@@ -18,12 +18,12 @@ package com.android.wm.shell.flicker.pip.apps
import android.platform.test.annotations.Postsubmit
import android.tools.Rotation
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.device.apphelpers.StandardAppHelper
import android.tools.flicker.junit.FlickerBuilderProvider
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.traces.component.ComponentNameMatcher
import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.Test
import org.junit.runners.Parameterized
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
index 88650107e63a..1fc9d9910a15 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -65,8 +65,8 @@ import org.junit.runners.Parameterized
open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
override val standardAppHelper: MapsAppHelper = MapsAppHelper(instrumentation)
- override val permissions: Array<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS,
- Manifest.permission.ACCESS_FINE_LOCATION)
+ override val permissions: Array<String> =
+ arrayOf(Manifest.permission.POST_NOTIFICATIONS, Manifest.permission.ACCESS_FINE_LOCATION)
val locationManager: LocationManager =
instrumentation.context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
index e85da30440cf..3a0eeb67995b 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.pip.apps
import android.Manifest
import android.platform.test.annotations.Postsubmit
import android.tools.Rotation
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.device.apphelpers.NetflixAppHelper
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.helpers.WindowUtils
+import android.tools.traces.component.ComponentNameMatcher
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
index 3ae5937df4d0..35ed8de3a464 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
@@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.pip.apps
import android.Manifest
import android.platform.test.annotations.Postsubmit
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.device.apphelpers.YouTubeAppHelper
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
import androidx.test.filters.RequiresDevice
import org.junit.Assume
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
index de8e7c3b35b1..879034f32514 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
@@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.pip.apps
import android.Manifest
import android.platform.test.annotations.Postsubmit
import android.tools.Rotation
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.device.apphelpers.YouTubeAppHelper
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.helpers.WindowUtils
+import android.tools.traces.component.ComponentNameMatcher
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
index dc122590388f..8cb81b46cf4d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
@@ -18,10 +18,10 @@ package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
import android.tools.Rotation
-import android.tools.traces.component.ComponentNameMatcher.Companion.LAUNCHER
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.traces.component.ComponentNameMatcher.Companion.LAUNCHER
import com.android.server.wm.flicker.helpers.setRotation
import org.junit.Test
import org.junit.runners.Parameterized
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
index 3d9eae62b499..6dd3a175da65 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
@@ -18,10 +18,10 @@ package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
import android.tools.Rotation
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.traces.component.ComponentNameMatcher
import org.junit.Test
import org.junit.runners.Parameterized
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
index 7b6839dc123f..0742cf9c5887 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
@@ -18,9 +18,9 @@ package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
import android.tools.Rotation
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.traces.component.ComponentNameMatcher
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import org.junit.Test
import org.junit.runners.Parameterized
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt
index f4baf5f75928..c4881e7e17a1 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt
@@ -18,9 +18,9 @@ package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
import android.tools.Rotation
-import android.tools.flicker.subject.region.RegionSubject
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.subject.region.RegionSubject
import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
import com.android.wm.shell.flicker.utils.Direction
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
index fd467e32e0dc..99c1ad2aaa4e 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
@@ -20,11 +20,11 @@ import android.app.Instrumentation
import android.content.Intent
import android.platform.test.annotations.Presubmit
import android.tools.Rotation
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import android.tools.helpers.WindowUtils
+import android.tools.traces.component.ComponentNameMatcher
import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.testapp.ActivityOptions
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
index 9e6a686861c8..bcd0f126daef 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -24,6 +24,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.MultiWindowUtils
import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
@@ -51,6 +52,10 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
fun setup() {
Assume.assumeTrue(tapl.isTablet)
+ MultiWindowUtils.executeShellCommand(
+ instrumentation,
+ "settings put system notification_cooldown_enabled 0"
+ )
// Send a notification
sendNotificationApp.launchViaIntent(wmHelper)
sendNotificationApp.postNotification(wmHelper)
@@ -74,5 +79,10 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
sendNotificationApp.exit(wmHelper)
+
+ MultiWindowUtils.executeShellCommand(
+ instrumentation,
+ "settings reset system notification_cooldown_enabled"
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index 9312c0aebf98..db962e717a3b 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -141,7 +141,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
private fun isLandscape(rotation: Rotation): Boolean {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return displayBounds.width > displayBounds.height
+ return displayBounds.width() > displayBounds.height()
}
private fun isTablet(): Boolean {
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index d74c59ef0879..7f48499b0558 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Presubmit
-import android.tools.traces.component.ComponentNameMatcher
-import android.tools.traces.component.EdgeExtensionComponentMatcher
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.traces.component.EdgeExtensionComponentMatcher
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
index 8724346427f4..a72b3d15eb9e 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
@@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Presubmit
import android.tools.NavBar
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.traces.component.ComponentNameMatcher
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.wm.shell.flicker.splitscreen.benchmark.SplitScreenBase
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
index 16d73318bd3a..90453640c91a 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
@@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.NavBar
-import android.tools.flicker.subject.layers.LayersTraceSubject
-import android.tools.flicker.subject.region.RegionSubject
-import android.tools.traces.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.flicker.subject.layers.LayersTraceSubject
+import android.tools.flicker.subject.region.RegionSubject
+import android.tools.traces.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.UnlockKeyguardToSplitScreenBenchmark
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
index 9c5a3fe35bfe..7e8e50843b90 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.traces.component.ComponentNameMatcher
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index 38206c396efb..6a6aa1abc9f3 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -128,7 +128,7 @@ abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: Legacy
private fun isLandscape(rotation: Rotation): Boolean {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return displayBounds.width > displayBounds.height
+ return displayBounds.width() > displayBounds.height()
}
companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index a19d232c9a2f..90d2635f6a51 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -17,8 +17,8 @@
package com.android.wm.shell.flicker
import android.app.Instrumentation
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
index 3df0954da2e9..509f4f202b6b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
@@ -18,13 +18,13 @@
package com.android.wm.shell.flicker.utils
+import android.graphics.Region
import android.tools.Rotation
-import android.tools.datatypes.Region
+import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.subject.layers.LayerTraceEntrySubject
import android.tools.flicker.subject.layers.LayersTraceSubject
-import android.tools.traces.component.IComponentMatcher
-import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.helpers.WindowUtils
+import android.tools.traces.component.IComponentMatcher
fun LegacyFlickerTest.appPairsDividerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
@@ -263,41 +263,41 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return invoke {
val dividerRegion =
- layer(SPLIT_SCREEN_DIVIDER_COMPONENT)?.visibleRegion?.region
+ layer(SPLIT_SCREEN_DIVIDER_COMPONENT)?.visibleRegion?.region?.bounds
?: error("$SPLIT_SCREEN_DIVIDER_COMPONENT component not found")
visibleRegion(component).isNotEmpty()
visibleRegion(component)
.coversAtMost(
- if (displayBounds.width > displayBounds.height) {
+ if (displayBounds.width() > displayBounds.height()) {
if (landscapePosLeft) {
- Region.from(
+ Region(
0,
0,
- (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
- displayBounds.bounds.bottom
+ (dividerRegion.left + dividerRegion.right) / 2,
+ displayBounds.bottom
)
} else {
- Region.from(
- (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+ Region(
+ (dividerRegion.left + dividerRegion.right) / 2,
0,
- displayBounds.bounds.right,
- displayBounds.bounds.bottom
+ displayBounds.right,
+ displayBounds.bottom
)
}
} else {
if (portraitPosTop) {
- Region.from(
+ Region(
0,
0,
- displayBounds.bounds.right,
- (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2
+ displayBounds.right,
+ (dividerRegion.top + dividerRegion.bottom) / 2
)
} else {
- Region.from(
+ Region(
0,
- (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2,
- displayBounds.bounds.right,
- displayBounds.bounds.bottom
+ (dividerRegion.top + dividerRegion.bottom) / 2,
+ displayBounds.right,
+ displayBounds.bottom
)
}
}
@@ -420,17 +420,17 @@ fun LegacyFlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd(
fun getPrimaryRegion(dividerRegion: Region, rotation: Rotation): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return if (rotation.isRotated()) {
- Region.from(
+ Region(
0,
0,
dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
- displayBounds.bounds.bottom
+ displayBounds.bottom
)
} else {
- Region.from(
+ Region(
0,
0,
- displayBounds.bounds.right,
+ displayBounds.right,
dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
)
}
@@ -439,18 +439,18 @@ fun getPrimaryRegion(dividerRegion: Region, rotation: Rotation): Region {
fun getSecondaryRegion(dividerRegion: Region, rotation: Rotation): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return if (rotation.isRotated()) {
- Region.from(
+ Region(
dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset,
0,
- displayBounds.bounds.right,
- displayBounds.bounds.bottom
+ displayBounds.right,
+ displayBounds.bottom
)
} else {
- Region.from(
+ Region(
0,
dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
- displayBounds.bounds.right,
- displayBounds.bounds.bottom
+ displayBounds.right,
+ displayBounds.bottom
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
index 50c04354528f..4465a16a8e0f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
@@ -17,8 +17,8 @@
package com.android.wm.shell.flicker.utils
import android.platform.test.annotations.Presubmit
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index 9cc3a989894e..c4954f90179c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -20,11 +20,11 @@ import android.app.Instrumentation
import android.graphics.Point
import android.os.SystemClock
import android.tools.Rotation
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.traces.component.ComponentNameMatcher
import android.tools.traces.component.IComponentMatcher
import android.tools.traces.component.IComponentNameMatcher
-import android.tools.device.apphelpers.StandardAppHelper
-import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.traces.parsers.WindowManagerStateHelper
import android.tools.traces.parsers.toFlickerComponent
import android.view.InputDevice
@@ -182,13 +182,7 @@ object SplitScreenUtils {
val swipeXCoordinate = displayBounds.centerX() / 2
// Pull down the notifications
- device.swipe(
- swipeXCoordinate,
- 5,
- swipeXCoordinate,
- displayBounds.bottom,
- 50 /* steps */
- )
+ device.swipe(swipeXCoordinate, 5, swipeXCoordinate, displayBounds.bottom, 50 /* steps */)
SystemClock.sleep(TIMEOUT_MS)
// Find the target notification
@@ -211,7 +205,7 @@ object SplitScreenUtils {
// Drag to split
val dragStart = notificationContent.visibleCenter
val dragMiddle = Point(dragStart.x + 50, dragStart.y)
- val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
+ val dragEnd = Point(displayBounds.width() / 4, displayBounds.width() / 4)
val downTime = SystemClock.uptimeMillis()
touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart)
@@ -318,7 +312,7 @@ object SplitScreenUtils {
wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
?: error("Display not found")
val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
- dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200)
+ dividerBar.drag(Point(displayBounds.width() * 1 / 3, displayBounds.height() * 2 / 3), 200)
wmHelper
.StateSyncBuilder()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 2ff1ddd8c0c8..9c623bd5b76f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -120,6 +120,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
private TestableContentResolver mContentResolver;
private TestableLooper mTestableLooper;
+ private CrossActivityBackAnimation mCrossActivityBackAnimation;
+ private CrossTaskBackAnimation mCrossTaskBackAnimation;
private ShellBackAnimationRegistry mShellBackAnimationRegistry;
@Before
@@ -133,11 +135,11 @@ public class BackAnimationControllerTest extends ShellTestCase {
ANIMATION_ENABLED);
mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
+ mCrossActivityBackAnimation = new CrossActivityBackAnimation(mContext, mAnimationBackground,
+ mRootTaskDisplayAreaOrganizer);
+ mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground);
mShellBackAnimationRegistry =
- new ShellBackAnimationRegistry(
- new CrossActivityBackAnimation(
- mContext, mAnimationBackground, mRootTaskDisplayAreaOrganizer),
- new CrossTaskBackAnimation(mContext, mAnimationBackground),
+ new ShellBackAnimationRegistry(mCrossActivityBackAnimation, mCrossTaskBackAnimation,
/* dialogCloseAnimation= */ null,
new CustomizeActivityAnimation(mContext, mAnimationBackground),
/* defaultBackToHomeAnimation= */ null);
@@ -576,16 +578,14 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Test
public void testBackToActivity() throws RemoteException {
- final CrossActivityBackAnimation animation = new CrossActivityBackAnimation(
- mContext, mAnimationBackground, mRootTaskDisplayAreaOrganizer);
- verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.getRunner());
+ verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ mCrossActivityBackAnimation.getRunner());
}
@Test
public void testBackToTask() throws RemoteException {
- final CrossTaskBackAnimation animation = new CrossTaskBackAnimation(mContext,
- mAnimationBackground);
- verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_TASK, animation.getRunner());
+ verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_TASK,
+ mCrossTaskBackAnimation.getRunner());
}
private void verifySystemBackBehavior(int type, BackAnimationRunner animation)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 48e396a4817f..6be411dd81d0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -1222,6 +1222,19 @@ public class BubbleDataTest extends ShellTestCase {
assertThat(update.bubbleBarLocation).isEqualTo(BubbleBarLocation.LEFT);
}
+ @Test
+ public void setSelectedBubbleAndExpandStack() {
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.setSelectedBubbleAndExpandStack(mBubbleA1);
+
+ verifyUpdateReceived();
+ assertSelectionChangedTo(mBubbleA1);
+ assertExpandedChangedTo(true);
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
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 c5e229feaba7..acc0bce5cce9 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
@@ -298,6 +298,32 @@ public class UnfoldTransitionHandlerTest {
}
@Test
+ public void fold_animationInProgress_finishesTransition() {
+ TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+ // Unfold
+ mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false);
+ mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+ mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ mock(TransitionInfo.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback
+ );
+
+ // Start animation but don't finish it
+ mShellUnfoldProgressProvider.onStateChangeStarted();
+ mShellUnfoldProgressProvider.onStateChangeProgress(0.5f);
+
+ // Fold
+ mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ true);
+
+ verify(finishCallback).onTransitionFinished(any());
+ }
+
+ @Test
public void mergeAnimation_eatsDisplayOnlyTransitions() {
TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 29bb1b9846b4..4706699039a6 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -538,6 +538,7 @@ cc_defaults {
"pipeline/skia/RenderNodeDrawable.cpp",
"pipeline/skia/ReorderBarrierDrawables.cpp",
"pipeline/skia/TransformCanvas.cpp",
+ "renderstate/RenderState.cpp",
"renderthread/Frame.cpp",
"renderthread/RenderTask.cpp",
"renderthread/TimeLord.cpp",
@@ -615,7 +616,6 @@ cc_defaults {
"pipeline/skia/SkiaVulkanPipeline.cpp",
"pipeline/skia/VkFunctorDrawable.cpp",
"pipeline/skia/VkInteropFunctorDrawable.cpp",
- "renderstate/RenderState.cpp",
"renderthread/CacheManager.cpp",
"renderthread/CanvasContext.cpp",
"renderthread/DrawFrameTask.cpp",
diff --git a/libs/hwui/platform/host/renderthread/RenderThread.cpp b/libs/hwui/platform/host/renderthread/RenderThread.cpp
index 6f08b5979772..f9d0f4704e08 100644
--- a/libs/hwui/platform/host/renderthread/RenderThread.cpp
+++ b/libs/hwui/platform/host/renderthread/RenderThread.cpp
@@ -17,6 +17,7 @@
#include "renderthread/RenderThread.h"
#include "Readback.h"
+#include "renderstate/RenderState.h"
#include "renderthread/VulkanManager.h"
namespace android {
@@ -66,6 +67,7 @@ RenderThread::RenderThread()
RenderThread::~RenderThread() {}
void RenderThread::initThreadLocals() {
+ mRenderState = new RenderState(*this);
mCacheManager = new CacheManager(*this);
}
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index e08d32a7735c..60657cf91123 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -16,11 +16,13 @@
#ifndef RENDERSTATE_H
#define RENDERSTATE_H
-#include "utils/Macros.h"
-
+#include <pthread.h>
#include <utils/RefBase.h>
+
#include <set>
+#include "utils/Macros.h"
+
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 2904dfe76f40..708b0113e13e 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -442,14 +442,17 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
}
// TODO: maybe we want to get rid of the WCG check if overlay properties just works?
- const bool canUseFp16 = DeviceInfo::get()->isSupportFp16ForHdr() ||
- DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType;
-
- if (canUseFp16) {
- if (mEglConfigF16 == EGL_NO_CONFIG_KHR) {
- colorMode = ColorMode::Default;
- } else {
- config = mEglConfigF16;
+ bool canUseFp16 = DeviceInfo::get()->isSupportFp16ForHdr() ||
+ DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType;
+
+ if (colorMode == ColorMode::Hdr) {
+ if (canUseFp16 && !DeviceInfo::get()->isSupportRgba10101010ForHdr()) {
+ if (mEglConfigF16 == EGL_NO_CONFIG_KHR) {
+ // If the driver doesn't support fp16 then fallback to 8-bit
+ canUseFp16 = false;
+ } else {
+ config = mEglConfigF16;
+ }
}
}
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index b8b03b6a0d2d..19e59a776511 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -1,5 +1,4 @@
package: "android.location.flags"
-container: "system"
flag {
name: "new_geocoder"
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 76bbca620072..5aa006bce000 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -1309,18 +1309,24 @@ public final class MediaRouter2 {
return;
}
- RoutingController newController;
- if (sessionInfo.isSystemSession()) {
- newController = getSystemController();
- newController.setRoutingSessionInfo(sessionInfo);
+ RoutingController newController = addRoutingController(sessionInfo);
+ notifyTransfer(oldController, newController);
+ }
+
+ @NonNull
+ private RoutingController addRoutingController(@NonNull RoutingSessionInfo session) {
+ RoutingController controller;
+ if (session.isSystemSession()) {
+ // mSystemController is never released, so we only need to update its status.
+ mSystemController.setRoutingSessionInfo(session);
+ controller = mSystemController;
} else {
- newController = new RoutingController(sessionInfo);
+ controller = new RoutingController(session);
synchronized (mLock) {
- mNonSystemRoutingControllers.put(newController.getId(), newController);
+ mNonSystemRoutingControllers.put(controller.getId(), controller);
}
}
-
- notifyTransfer(oldController, newController);
+ return controller;
}
void updateControllerOnHandler(RoutingSessionInfo sessionInfo) {
@@ -1329,40 +1335,12 @@ public final class MediaRouter2 {
return;
}
- if (sessionInfo.isSystemSession()) {
- // The session info is sent from SystemMediaRoute2Provider.
- RoutingController systemController = getSystemController();
- systemController.setRoutingSessionInfo(sessionInfo);
- notifyControllerUpdated(systemController);
- return;
- }
-
- RoutingController matchingController;
- synchronized (mLock) {
- matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
- }
-
- if (matchingController == null) {
- Log.w(
- TAG,
- "updateControllerOnHandler: Matching controller not found. uniqueSessionId="
- + sessionInfo.getId());
- return;
- }
-
- RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
- if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
- Log.w(
- TAG,
- "updateControllerOnHandler: Provider IDs are not matched. old="
- + oldInfo.getProviderId()
- + ", new="
- + sessionInfo.getProviderId());
- return;
+ RoutingController controller =
+ getMatchingController(sessionInfo, /* logPrefix */ "updateControllerOnHandler");
+ if (controller != null) {
+ controller.setRoutingSessionInfo(sessionInfo);
+ notifyControllerUpdated(controller);
}
-
- matchingController.setRoutingSessionInfo(sessionInfo);
- notifyControllerUpdated(matchingController);
}
void releaseControllerOnHandler(RoutingSessionInfo sessionInfo) {
@@ -1371,34 +1349,47 @@ public final class MediaRouter2 {
return;
}
- RoutingController matchingController;
- synchronized (mLock) {
- matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
+ RoutingController matchingController =
+ getMatchingController(sessionInfo, /* logPrefix */ "releaseControllerOnHandler");
+
+ if (matchingController != null) {
+ matchingController.releaseInternal(/* shouldReleaseSession= */ false);
}
+ }
+
+ @Nullable
+ private RoutingController getMatchingController(
+ RoutingSessionInfo sessionInfo, String logPrefix) {
+ if (sessionInfo.isSystemSession()) {
+ return getSystemController();
+ } else {
+ RoutingController controller;
+ synchronized (mLock) {
+ controller = mNonSystemRoutingControllers.get(sessionInfo.getId());
+ }
- if (matchingController == null) {
- if (DEBUG) {
- Log.d(
+ if (controller == null) {
+ Log.w(
TAG,
- "releaseControllerOnHandler: Matching controller not found. "
- + "uniqueSessionId="
+ logPrefix
+ + ": Matching controller not found. uniqueSessionId="
+ sessionInfo.getId());
+ return null;
}
- return;
- }
- RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
- if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
- Log.w(
- TAG,
- "releaseControllerOnHandler: Provider IDs are not matched. old="
- + oldInfo.getProviderId()
- + ", new="
- + sessionInfo.getProviderId());
- return;
+ RoutingSessionInfo oldInfo = controller.getRoutingSessionInfo();
+ if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
+ Log.w(
+ TAG,
+ logPrefix
+ + ": Provider IDs are not matched. old="
+ + oldInfo.getProviderId()
+ + ", new="
+ + sessionInfo.getProviderId());
+ return null;
+ }
+ return controller;
}
-
- matchingController.releaseInternal(/* shouldReleaseSession= */ false);
}
void onRequestCreateControllerByManagerOnHandler(
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
index bf6ec9635912..5bf1b4ef96ff 100644
--- a/media/java/android/media/flags/editing.aconfig
+++ b/media/java/android/media/flags/editing.aconfig
@@ -1,5 +1,4 @@
package: "com.android.media.editing.flags"
-container: "system"
flag {
name: "add_media_metrics_editing"
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 91c4f118658a..8d6982e2b122 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -1,5 +1,4 @@
package: "com.android.media.flags"
-container: "system"
flag {
name: "enable_rlp_callbacks_in_media_router2"
diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig
index 9a9a0735d089..b16580927fa6 100644
--- a/media/java/android/media/flags/projection.aconfig
+++ b/media/java/android/media/flags/projection.aconfig
@@ -1,5 +1,4 @@
package: "com.android.media.projection.flags"
-container: "system"
# Project link: https://gantry.corp.google.com/projects/android_platform_window_surfaces/changes
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 207ccbee0b50..871e9ab87299 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -80,4 +80,7 @@ interface ISessionManager {
boolean hasCustomMediaSessionPolicyProvider(String componentName);
int getSessionPolicies(in MediaSession.Token token);
void setSessionPolicies(in MediaSession.Token token, int policies);
+
+ // For testing of temporarily engaged sessions.
+ void expireTempEngagedSessions();
}
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 97971e134f02..1731e5e4335c 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -1,5 +1,4 @@
package: "android.media.tv.flags"
-container: "system"
flag {
name: "broadcast_visibility_types"
diff --git a/media/jni/playback_flags.aconfig b/media/jni/playback_flags.aconfig
index 9d927eccc596..2bb0ec5375fd 100644
--- a/media/jni/playback_flags.aconfig
+++ b/media/jni/playback_flags.aconfig
@@ -1,5 +1,4 @@
package: "com.android.media.playback.flags"
-container: "system"
flag {
name: "mediametadataretriever_default_rgba8888"
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 8227bdbd14a7..882afcab6290 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -41,16 +41,15 @@ using namespace aidl::android::os;
using namespace std::chrono_literals;
-using HalSessionHint = aidl::android::hardware::power::SessionHint;
-using HalSessionMode = aidl::android::hardware::power::SessionMode;
-using HalWorkDuration = aidl::android::hardware::power::WorkDuration;
+// Namespace for AIDL types coming from the PowerHAL
+namespace hal = aidl::android::hardware::power;
using android::base::StringPrintf;
struct APerformanceHintSession;
constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
-struct AWorkDuration : public HalWorkDuration {};
+struct AWorkDuration : public hal::WorkDuration {};
struct APerformanceHintManager {
public:
@@ -115,7 +114,7 @@ private:
// Last hint reported from sendHint indexed by hint value
std::vector<int64_t> mLastHintSentTimestamp;
// Cached samples
- std::vector<HalWorkDuration> mActualWorkDurations;
+ std::vector<hal::WorkDuration> mActualWorkDurations;
std::string mSessionName;
static int32_t sIDCounter;
// The most recent set of thread IDs
@@ -207,8 +206,9 @@ APerformanceHintSession::APerformanceHintSession(std::shared_ptr<IHintManager> h
mTargetDurationNanos(targetDurationNanos),
mFirstTargetMetTimestamp(0),
mLastTargetMetTimestamp(0) {
- const std::vector<HalSessionHint> sessionHintRange{ndk::enum_range<HalSessionHint>().begin(),
- ndk::enum_range<HalSessionHint>().end()};
+ const std::vector<hal::SessionHint> sessionHintRange{ndk::enum_range<hal::SessionHint>()
+ .begin(),
+ ndk::enum_range<hal::SessionHint>().end()};
mLastHintSentTimestamp = std::vector<int64_t>(sessionHintRange.size(), 0);
mSessionName = android::base::StringPrintf("ADPF Session %" PRId32, ++sIDCounter);
@@ -246,10 +246,10 @@ int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNano
}
int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNanos) {
- HalWorkDuration workDuration{.durationNanos = actualDurationNanos,
- .workPeriodStartTimestampNanos = 0,
- .cpuDurationNanos = actualDurationNanos,
- .gpuDurationNanos = 0};
+ hal::WorkDuration workDuration{.durationNanos = actualDurationNanos,
+ .workPeriodStartTimestampNanos = 0,
+ .cpuDurationNanos = actualDurationNanos,
+ .gpuDurationNanos = 0};
return reportActualWorkDurationInternal(static_cast<AWorkDuration*>(&workDuration));
}
@@ -323,7 +323,8 @@ int APerformanceHintSession::getThreadIds(int32_t* const threadIds, size_t* size
int APerformanceHintSession::setPreferPowerEfficiency(bool enabled) {
ndk::ScopedAStatus ret =
- mHintSession->setMode(static_cast<int32_t>(HalSessionMode::POWER_EFFICIENCY), enabled);
+ mHintSession->setMode(static_cast<int32_t>(hal::SessionMode::POWER_EFFICIENCY),
+ enabled);
if (!ret.isOk()) {
ALOGE("%s: HintSession setPreferPowerEfficiency failed: %s", __FUNCTION__,
diff --git a/nfc/Android.bp b/nfc/Android.bp
index c186804d2006..13ac2311bde3 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -66,6 +66,7 @@ java_sdk_library {
],
impl_library_visibility: [
"//frameworks/base:__subpackages__",
+ "//cts/hostsidetests/multidevices/nfc:__subpackages__",
"//cts/tests/tests/nfc",
"//packages/apps/Nfc:__subpackages__",
],
diff --git a/nfc/TEST_MAPPING b/nfc/TEST_MAPPING
index 5b5ea3790010..49c778d22038 100644
--- a/nfc/TEST_MAPPING
+++ b/nfc/TEST_MAPPING
@@ -5,6 +5,9 @@
},
{
"name": "CtsNfcTestCases"
+ },
+ {
+ "name": "CtsNdefTestCases"
}
]
}
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 73b29db0f317..778f07c64a0a 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.nfc"
-container: "system"
flag {
name: "enable_nfc_mainline"
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index 0af108052137..e8e24f492005 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -40,6 +40,7 @@
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center</item>
+ <item name="android:textDirection">locale</item>
<item name="android:layout_marginLeft">14dp</item>
<item name="android:layout_marginRight">14dp</item>
<item name="android:textSize">20sp</item>
@@ -53,6 +54,7 @@
<item name="android:layout_marginTop">18dp</item>
<item name="android:layout_marginLeft">18dp</item>
<item name="android:layout_marginRight">18dp</item>
+ <item name="android:textDirection">locale</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
diff --git a/packages/CrashRecovery/aconfig/flags.aconfig b/packages/CrashRecovery/aconfig/flags.aconfig
index 35d7393ba517..8627eac7beed 100644
--- a/packages/CrashRecovery/aconfig/flags.aconfig
+++ b/packages/CrashRecovery/aconfig/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.crashrecovery.flags"
-container: "system"
flag {
name: "recoverability_detection"
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index 6f20adf74ee2..75a8bdfe5416 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -39,15 +39,15 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
-import android.util.LongArrayQueue;
import android.util.Slog;
import android.util.Xml;
+import android.utils.BackgroundThread;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index 271d552fc574..f86eb61c365f 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -31,7 +31,6 @@ import android.content.pm.VersionedPackage;
import android.crashrecovery.flags.Flags;
import android.os.Build;
import android.os.Environment;
-import android.os.FileUtils;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.SystemClock;
@@ -44,10 +43,11 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import android.utils.ArrayUtils;
+import android.utils.FileUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
new file mode 100644
index 000000000000..fa4d6afc03d3
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.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.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.File;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
+ *
+ * @hide
+ */
+public class ArrayUtils {
+ private ArrayUtils() { /* cannot be instantiated */ }
+ public static final File[] EMPTY_FILE = new File[0];
+
+
+ /**
+ * Return first index of {@code value} in {@code array}, or {@code -1} if
+ * not found.
+ */
+ public static <T> int indexOf(@Nullable T[] array, T value) {
+ if (array == null) return -1;
+ for (int i = 0; i < array.length; i++) {
+ if (Objects.equals(array[i], value)) return i;
+ }
+ return -1;
+ }
+
+ /** @hide */
+ public static @NonNull File[] defeatNullable(@Nullable File[] val) {
+ return (val != null) ? val : EMPTY_FILE;
+ }
+
+ /**
+ * Checks if given array is null or has zero elements.
+ */
+ public static boolean isEmpty(@Nullable int[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * True if the byte array is null or has length 0.
+ */
+ public static boolean isEmpty(@Nullable byte[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * Converts from List of bytes to byte array
+ * @param list
+ * @return byte[]
+ */
+ public static byte[] toPrimitive(List<byte[]> list) {
+ if (list.size() == 0) {
+ return new byte[0];
+ }
+ int byteLen = list.get(0).length;
+ byte[] array = new byte[list.size() * byteLen];
+ for (int i = 0; i < list.size(); i++) {
+ for (int j = 0; j < list.get(i).length; j++) {
+ array[i * byteLen + j] = list.get(i)[j];
+ }
+ }
+ return array;
+ }
+
+ /**
+ * Adds value to given array if not already present, providing set-like
+ * behavior.
+ */
+ public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
+ return appendInt(cur, val, false);
+ }
+
+ /**
+ * Adds value to given array.
+ */
+ public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
+ boolean allowDuplicates) {
+ if (cur == null) {
+ return new int[] { val };
+ }
+ final int n = cur.length;
+ if (!allowDuplicates) {
+ for (int i = 0; i < n; i++) {
+ if (cur[i] == val) {
+ return cur;
+ }
+ }
+ }
+ int[] ret = new int[n + 1];
+ System.arraycopy(cur, 0, ret, 0, n);
+ ret[n] = val;
+ return ret;
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
new file mode 100644
index 000000000000..afcf6895fd0d
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
@@ -0,0 +1,103 @@
+/*
+ * * 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.utils;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Thread for asynchronous event processing. This thread is configured as
+ * {@link android.os.Process#THREAD_PRIORITY_BACKGROUND}, which means fewer CPU
+ * resources will be dedicated to it, and it will "have less chance of impacting
+ * the responsiveness of the user interface."
+ * <p>
+ * This thread is best suited for tasks that the user is not actively waiting
+ * for, or for tasks that the user expects to be executed eventually.
+ *
+ * @see com.android.internal.os.BackgroundThread
+ *
+ * TODO: b/326916057 depend on modules-utils-backgroundthread instead
+ * @hide
+ */
+public final class BackgroundThread extends HandlerThread {
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static BackgroundThread sInstance;
+ @GuardedBy("sLock")
+ private static Handler sHandler;
+ @GuardedBy("sLock")
+ private static HandlerExecutor sHandlerExecutor;
+
+ private BackgroundThread() {
+ super(BackgroundThread.class.getName(), android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ }
+
+ @GuardedBy("sLock")
+ private static void ensureThreadLocked() {
+ if (sInstance == null) {
+ sInstance = new BackgroundThread();
+ sInstance.start();
+ sHandler = new Handler(sInstance.getLooper());
+ sHandlerExecutor = new HandlerExecutor(sHandler);
+ }
+ }
+
+ /**
+ * Get the singleton instance of this class.
+ *
+ * @return the singleton instance of this class
+ */
+ @NonNull
+ public static BackgroundThread get() {
+ synchronized (sLock) {
+ ensureThreadLocked();
+ return sInstance;
+ }
+ }
+
+ /**
+ * Get the singleton {@link Handler} for this class.
+ *
+ * @return the singleton {@link Handler} for this class.
+ */
+ @NonNull
+ public static Handler getHandler() {
+ synchronized (sLock) {
+ ensureThreadLocked();
+ return sHandler;
+ }
+ }
+
+ /**
+ * Get the singleton {@link Executor} for this class.
+ *
+ * @return the singleton {@link Executor} for this class.
+ */
+ @NonNull
+ public static Executor getExecutor() {
+ synchronized (sLock) {
+ ensureThreadLocked();
+ return sHandlerExecutor;
+ }
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
new file mode 100644
index 000000000000..e4923bfc4ecb
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Bits and pieces copied from hidden API of android.os.FileUtils.
+ *
+ * @hide
+ */
+public class FileUtils {
+ /**
+ * Read a text file into a String, optionally limiting the length.
+ *
+ * @param file to read (will not seek, so things like /proc files are OK)
+ * @param max length (positive for head, negative of tail, 0 for no limit)
+ * @param ellipsis to add of the file was truncated (can be null)
+ * @return the contents of the file, possibly truncated
+ * @throws IOException if something goes wrong reading the file
+ * @hide
+ */
+ public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
+ @Nullable String ellipsis) throws IOException {
+ InputStream input = new FileInputStream(file);
+ // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
+ // input stream, bytes read not equal to buffer size is not necessarily the correct
+ // indication for EOF; but it is true for BufferedInputStream due to its implementation.
+ BufferedInputStream bis = new BufferedInputStream(input);
+ try {
+ long size = file.length();
+ if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
+ if (size > 0 && (max == 0 || size < max)) max = (int) size;
+ byte[] data = new byte[max + 1];
+ int length = bis.read(data);
+ if (length <= 0) return "";
+ if (length <= max) return new String(data, 0, length);
+ if (ellipsis == null) return new String(data, 0, max);
+ return new String(data, 0, max) + ellipsis;
+ } else if (max < 0) { // "tail" mode: keep the last N
+ int len;
+ boolean rolled = false;
+ byte[] last = null;
+ byte[] data = null;
+ do {
+ if (last != null) rolled = true;
+ byte[] tmp = last;
+ last = data;
+ data = tmp;
+ if (data == null) data = new byte[-max];
+ len = bis.read(data);
+ } while (len == data.length);
+
+ if (last == null && len <= 0) return "";
+ if (last == null) return new String(data, 0, len);
+ if (len > 0) {
+ rolled = true;
+ System.arraycopy(last, len, last, 0, last.length - len);
+ System.arraycopy(data, 0, last, last.length - len, len);
+ }
+ if (ellipsis == null || !rolled) return new String(last);
+ return ellipsis + new String(last);
+ } else { // "cat" mode: size unknown, read it all in streaming fashion
+ ByteArrayOutputStream contents = new ByteArrayOutputStream();
+ int len;
+ byte[] data = new byte[1024];
+ do {
+ len = bis.read(data);
+ if (len > 0) contents.write(data, 0, len);
+ } while (len == data.length);
+ return contents.toString();
+ }
+ } finally {
+ bis.close();
+ input.close();
+ }
+ }
+
+ /**
+ * Perform an fsync on the given FileOutputStream. The stream at this
+ * point must be flushed but not yet closed.
+ *
+ * @hide
+ */
+ public static boolean sync(FileOutputStream stream) {
+ try {
+ if (stream != null) {
+ stream.getFD().sync();
+ }
+ return true;
+ } catch (IOException e) {
+ }
+ return false;
+ }
+
+ /**
+ * List the files in the directory or return empty file.
+ *
+ * @hide
+ */
+ public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
+ return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
+ : ArrayUtils.EMPTY_FILE;
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
new file mode 100644
index 000000000000..fdb15e2333d5
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * 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.utils;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * An adapter {@link Executor} that posts all executed tasks onto the given
+ * {@link Handler}.
+ *
+ * TODO: b/326916057 depend on modules-utils-backgroundthread instead
+ * @hide
+ */
+public class HandlerExecutor implements Executor {
+ private final Handler mHandler;
+
+ public HandlerExecutor(@NonNull Handler handler) {
+ mHandler = Objects.requireNonNull(handler);
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ if (!mHandler.post(command)) {
+ throw new RejectedExecutionException(mHandler + " is shutting down");
+ }
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
new file mode 100644
index 000000000000..5cdc2536129a
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
@@ -0,0 +1,188 @@
+/*
+ * 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.utils;
+
+import libcore.util.EmptyArray;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
+ *
+ * @hide
+ */
+public class LongArrayQueue {
+
+ private long[] mValues;
+ private int mSize;
+ private int mHead;
+ private int mTail;
+
+ private long[] newUnpaddedLongArray(int num) {
+ return new long[num];
+ }
+ /**
+ * Initializes a queue with the given starting capacity.
+ *
+ * @param initialCapacity the capacity.
+ */
+ public LongArrayQueue(int initialCapacity) {
+ if (initialCapacity == 0) {
+ mValues = EmptyArray.LONG;
+ } else {
+ mValues = newUnpaddedLongArray(initialCapacity);
+ }
+ mSize = 0;
+ mHead = mTail = 0;
+ }
+
+ /**
+ * Initializes a queue with default starting capacity.
+ */
+ public LongArrayQueue() {
+ this(16);
+ }
+
+ /** @hide */
+ public static int growSize(int currentSize) {
+ return currentSize <= 4 ? 8 : currentSize * 2;
+ }
+
+ private void grow() {
+ if (mSize < mValues.length) {
+ throw new IllegalStateException("Queue not full yet!");
+ }
+ final int newSize = growSize(mSize);
+ final long[] newArray = newUnpaddedLongArray(newSize);
+ final int r = mValues.length - mHead; // Number of elements on and to the right of head.
+ System.arraycopy(mValues, mHead, newArray, 0, r);
+ System.arraycopy(mValues, 0, newArray, r, mHead);
+ mValues = newArray;
+ mHead = 0;
+ mTail = mSize;
+ }
+
+ /**
+ * Returns the number of elements in the queue.
+ */
+ public int size() {
+ return mSize;
+ }
+
+ /**
+ * Removes all elements from this queue.
+ */
+ public void clear() {
+ mSize = 0;
+ mHead = mTail = 0;
+ }
+
+ /**
+ * Adds a value to the tail of the queue.
+ *
+ * @param value the value to be added.
+ */
+ public void addLast(long value) {
+ if (mSize == mValues.length) {
+ grow();
+ }
+ mValues[mTail] = value;
+ mTail = (mTail + 1) % mValues.length;
+ mSize++;
+ }
+
+ /**
+ * Removes an element from the head of the queue.
+ *
+ * @return the element at the head of the queue.
+ * @throws NoSuchElementException if the queue is empty.
+ */
+ public long removeFirst() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ final long ret = mValues[mHead];
+ mHead = (mHead + 1) % mValues.length;
+ mSize--;
+ return ret;
+ }
+
+ /**
+ * Returns the element at the given position from the head of the queue, where 0 represents the
+ * head of the queue.
+ *
+ * @param position the position from the head of the queue.
+ * @return the element found at the given position.
+ * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
+ * {@code position} >= {@link #size()}
+ */
+ public long get(int position) {
+ if (position < 0 || position >= mSize) {
+ throw new IndexOutOfBoundsException("Index " + position
+ + " not valid for a queue of size " + mSize);
+ }
+ final int index = (mHead + position) % mValues.length;
+ return mValues[index];
+ }
+
+ /**
+ * Returns the element at the head of the queue, without removing it.
+ *
+ * @return the element at the head of the queue.
+ * @throws NoSuchElementException if the queue is empty
+ */
+ public long peekFirst() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ return mValues[mHead];
+ }
+
+ /**
+ * Returns the element at the tail of the queue.
+ *
+ * @return the element at the tail of the queue.
+ * @throws NoSuchElementException if the queue is empty.
+ */
+ public long peekLast() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
+ return mValues[index];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ if (mSize <= 0) {
+ return "{}";
+ }
+
+ final StringBuilder buffer = new StringBuilder(mSize * 64);
+ buffer.append('{');
+ buffer.append(get(0));
+ for (int i = 1; i < mSize; i++) {
+ buffer.append(", ");
+ buffer.append(get(i));
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
new file mode 100644
index 000000000000..dbbef61f6777
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
@@ -0,0 +1,118 @@
+/*
+ * 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.utils;
+
+import android.annotation.NonNull;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import libcore.util.XmlObjectFactory;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java
+ *
+ * @hide
+ */
+public class XmlUtils {
+
+ private static final String STRING_ARRAY_SEPARATOR = ":";
+
+ /** @hide */
+ public static final void beginDocument(XmlPullParser parser, String firstElementName)
+ throws XmlPullParserException, IOException {
+ int type;
+ while ((type = parser.next()) != parser.START_TAG
+ && type != parser.END_DOCUMENT) {
+ // Do nothing
+ }
+
+ if (type != parser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ if (!parser.getName().equals(firstElementName)) {
+ throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
+ + ", expected " + firstElementName);
+ }
+ }
+
+ /** @hide */
+ public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
+ throws IOException, XmlPullParserException {
+ for (;;) {
+ int type = parser.next();
+ if (type == XmlPullParser.END_DOCUMENT
+ || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
+ return false;
+ }
+ if (type == XmlPullParser.START_TAG
+ && parser.getDepth() == outerDepth + 1) {
+ return true;
+ }
+ }
+ }
+
+ private static XmlPullParser newPullParser() {
+ try {
+ XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ return parser;
+ } catch (XmlPullParserException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /** @hide */
+ public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
+ throws IOException {
+ final byte[] magic = new byte[4];
+ if (in instanceof FileInputStream) {
+ try {
+ Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ } else {
+ if (!in.markSupported()) {
+ in = new BufferedInputStream(in);
+ }
+ in.mark(8);
+ in.read(magic);
+ in.reset();
+ }
+
+ final TypedXmlPullParser xml;
+ xml = (TypedXmlPullParser) newPullParser();
+ try {
+ xml.setInput(in, "UTF_8");
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ return xml;
+ }
+}
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index bc35a85e48f8..9fd386f38684 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -68,6 +68,13 @@
<string name="choose_create_option_password_title">Save password to sign in to <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string>
<!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] -->
<string name="choose_create_option_sign_in_title">Save sign-in info for <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string>
+ <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create passkey flow. [CHAR LIMIT=200] -->
+ <string name="choose_create_single_tap_passkey_title">Use your screen lock to create a passkey for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
+ <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create password flow. [CHAR LIMIT=200] -->
+ <string name="choose_create_single_tap_password_title">Use your screen lock to create a password for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
+ <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create flow when the credential type is others. [CHAR LIMIT=200] -->
+ <!-- TODO(b/326243891) : Confirm with team on dynamically setting this based on recent product and ux discussions (does not disrupt e2e) -->
+ <string name="choose_create_single_tap_sign_in_title">Use your screen lock to save sign in info for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
<!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] -->
<string name="passkey">passkey</string>
<string name="password">password</string>
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
index f2c252ec6422..9c8ec3b56813 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
@@ -51,6 +51,8 @@ import com.android.credentialmanager.TAG
import com.android.credentialmanager.model.BiometricRequestInfo
import com.android.credentialmanager.model.EntryInfo
+const val CREDENTIAL_ENTRY_PREFIX = "androidx.credentials.provider.credentialEntry."
+
fun EntryInfo.getIntentSenderRequest(
isAutoSelected: Boolean = false
): IntentSenderRequest? {
@@ -140,7 +142,8 @@ private fun getCredentialOptionInfoList(
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
- biometricRequest = predetermineAndValidateBiometricFlow(it),
+ biometricRequest = retrieveEntryBiometricRequest(it,
+ CREDENTIAL_ENTRY_PREFIX),
)
)
}
@@ -169,7 +172,8 @@ private fun getCredentialOptionInfoList(
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
- biometricRequest = predetermineAndValidateBiometricFlow(it),
+ biometricRequest = retrieveEntryBiometricRequest(it,
+ CREDENTIAL_ENTRY_PREFIX),
)
)
}
@@ -197,7 +201,8 @@ private fun getCredentialOptionInfoList(
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
- biometricRequest = predetermineAndValidateBiometricFlow(it),
+ biometricRequest = retrieveEntryBiometricRequest(it,
+ CREDENTIAL_ENTRY_PREFIX),
)
)
}
@@ -211,27 +216,32 @@ private fun getCredentialOptionInfoList(
}
/**
- * This validates if this is a biometric flow or not, and if it is, this returns the expected
+ * This validates if the entry calling this method contains biometric info, and if so, returns a
* [BiometricRequestInfo]. Namely, the biometric flow must have at least the
* ALLOWED_AUTHENTICATORS bit passed from Jetpack.
* Note that the required values, such as the provider info's icon or display name, or the entries
* credential type or userName, and finally the display info's app name, are non-null and must
* exist to run through the flow.
+ *
+ * @param hintPrefix a string prefix indicating the type of entry being utilized, since both create
+ * and get flows utilize slice params; includes the final '.' before the name of the type (e.g.
+ * androidx.credentials.provider.credentialEntry.SLICE_HINT_ALLOWED_AUTHENTICATORS must have
+ * 'hintPrefix' up to "androidx.credentials.provider.credentialEntry.")
* // TODO(b/326243754) : Presently, due to dependencies, the opId bit is parsed but is never
* // expected to be used. When it is added, it should be lightly validated.
*/
-private fun predetermineAndValidateBiometricFlow(
- it: Entry
+fun retrieveEntryBiometricRequest(
+ entry: Entry,
+ hintPrefix: String,
): BiometricRequestInfo? {
// TODO(b/326243754) : When available, use the official jetpack structured type
- val allowedAuthenticators: Int? = it.slice.items.firstOrNull {
- it.hasHint("androidx.credentials." +
- "provider.credentialEntry.SLICE_HINT_ALLOWED_AUTHENTICATORS")
+ val allowedAuthenticators: Int? = entry.slice.items.firstOrNull {
+ it.hasHint(hintPrefix + "SLICE_HINT_ALLOWED_AUTHENTICATORS")
}?.int
// This is optional and does not affect validating the biometric flow in any case
- val opId: Int? = it.slice.items.firstOrNull {
- it.hasHint("androidx.credentials.provider.credentialEntry.SLICE_HINT_CRYPTO_OP_ID")
+ val opId: Int? = entry.slice.items.firstOrNull {
+ it.hasHint(hintPrefix + "SLICE_HINT_CRYPTO_OP_ID")
}?.int
if (allowedAuthenticators != null) {
return BiometricRequestInfo(opId = opId, allowedAuthenticators = allowedAuthenticators)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 28c40479962e..888777e81d65 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -18,6 +18,7 @@ package com.android.credentialmanager
import android.app.Activity
import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResult
import android.os.IBinder
import android.text.TextUtils
import android.util.Log
@@ -29,6 +30,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.common.BiometricFlowType
+import com.android.credentialmanager.common.BiometricPromptState
import com.android.credentialmanager.common.BiometricResult
import com.android.credentialmanager.common.BiometricState
import com.android.credentialmanager.model.EntryInfo
@@ -39,6 +42,7 @@ import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.isBiometricFlow
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
import com.android.credentialmanager.logging.LifecycleEvent
@@ -301,10 +305,24 @@ class CredentialSelectorViewModel(
}
fun createFlowOnEntrySelectedFromMoreOptionScreen(activeEntry: ActiveEntry) {
+ val isBiometricFlow = isBiometricFlow(activeEntry = activeEntry, isAutoSelectFlow = false)
+ if (isBiometricFlow) {
+ // This atomically ensures that the only edge case that *restarts* the biometric flow
+ // doesn't risk a configuration change bug on the more options page during create.
+ // Namely, it's atomic in that it happens only on a tap, and it is not possible to
+ // reproduce a tap and a rotation at the same time. However, even if it were, it would
+ // just be an alternate way to jump back into the biometric selection flow after this
+ // reset, and thus, the state machine is maintained.
+ onBiometricPromptStateChange(BiometricPromptState.INACTIVE)
+ }
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
currentScreenState =
- if (uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds
+ // An autoselect flow never makes it to the more options screen
+ if (isBiometricFlow) {
+ CreateScreenState.BIOMETRIC_SELECTION
+ } else if (
+ uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds
?.contains(activeEntry.activeProvider.id) ?: true ||
!(uiState.createCredentialUiState?.foundCandidateFromUserDefaultProvider
?: false) ||
@@ -330,7 +348,10 @@ class CredentialSelectorViewModel(
)
}
- fun createFlowOnEntrySelected(selectedEntry: EntryInfo) {
+ fun createFlowOnEntrySelected(
+ selectedEntry: EntryInfo,
+ authResult: AuthenticationResult? = null
+ ) {
val providerId = selectedEntry.providerId
val entryKey = selectedEntry.entryKey
val entrySubkey = selectedEntry.entrySubkey
@@ -341,6 +362,9 @@ class CredentialSelectorViewModel(
uiState = uiState.copy(
selectedEntry = selectedEntry,
providerActivityState = ProviderActivityState.READY_TO_LAUNCH,
+ biometricState = if (authResult == null) uiState.biometricState else uiState
+ .biometricState.copy(biometricResult = BiometricResult(
+ biometricAuthenticationResult = authResult))
)
} else {
credManRepo.onOptionSelected(
@@ -363,13 +387,48 @@ class CredentialSelectorViewModel(
}
}
+ /**************************************************************************/
+ /***** Biometric Flow Callbacks *****/
+ /**************************************************************************/
+
+ /**
+ * This allows falling back from the biometric prompt screen to the normal get flow by applying
+ * a reset to all necessary states involved in the fallback.
+ */
+ fun fallbackFromBiometricToNormalFlow(biometricFlowType: BiometricFlowType) {
+ onBiometricPromptStateChange(BiometricPromptState.INACTIVE)
+ when (biometricFlowType) {
+ BiometricFlowType.GET -> getFlowOnBackToPrimarySelectionScreen()
+ BiometricFlowType.CREATE -> createFlowOnUseOnceSelected()
+ }
+ }
+
+ /**
+ * This method can be used to change the [BiometricPromptState] according to the necessity.
+ * For example, if resetting, one might use [BiometricPromptState.INACTIVE], but if the flow
+ * has just launched, to avoid configuration errors, one can use
+ * [BiometricPromptState.PENDING].
+ */
+ fun onBiometricPromptStateChange(biometricPromptState: BiometricPromptState) {
+ uiState = uiState.copy(
+ biometricState = uiState.biometricState.copy(
+ biometricStatus = biometricPromptState
+ )
+ )
+ }
+
+ /**
+ * This returns the present biometric state.
+ */
+ fun getBiometricPromptState(): BiometricPromptState =
+ uiState.biometricState.biometricStatus
+
+ /**************************************************************************/
+ /***** Misc. Callbacks/Logs *****/
+ /**************************************************************************/
+
@Composable
fun logUiEvent(uiEventEnum: UiEventEnum) {
this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName)
}
-
- companion object {
- // TODO(b/326243754) : Replace/remove once all failure flows added in
- const val TEMPORARY_FAILURE_CODE = Integer.MIN_VALUE
- }
} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index fd6fc6a44c7c..c477f30a1d2f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -52,10 +52,12 @@ import androidx.credentials.provider.CreateEntry
import androidx.credentials.provider.RemoteEntry
import org.json.JSONObject
import android.credentials.flags.Flags
+import com.android.credentialmanager.createflow.isBiometricFlow
+import com.android.credentialmanager.createflow.isFlowAutoSelectable
import com.android.credentialmanager.getflow.TopBrandingContent
+import com.android.credentialmanager.ktx.retrieveEntryBiometricRequest
import java.time.Instant
-
fun getAppLabel(
pm: PackageManager,
appPackageName: String
@@ -237,6 +239,9 @@ class GetFlowUtils {
class CreateFlowUtils {
companion object {
+
+ private const val CREATE_ENTRY_PREFIX = "androidx.credentials.provider.createEntry."
+
/**
* Note: caller required handle empty list due to parsing error.
*/
@@ -417,12 +422,26 @@ class CreateFlowUtils {
}
}
val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser
+ val sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
+ compareByDescending { it.first.lastUsedTime }
+ )
+ val activeEntry = toActiveEntry(
+ defaultProvider = defaultProvider,
+ sortedCreateOptionsPairs = sortedCreateOptionsPairs,
+ remoteEntry = remoteEntry,
+ remoteEntryProvider = remoteEntryProvider,
+ )
+ val isBiometricFlow = if (activeEntry == null) false else isBiometricFlow(activeEntry,
+ isFlowAutoSelectable(
+ requestDisplayInfo = requestDisplayInfo,
+ activeEntry = activeEntry,
+ sortedCreateOptionsPairs = sortedCreateOptionsPairs
+ )
+ )
val initialScreenState = toCreateScreenState(
createOptionSize = createOptionsPairs.size,
remoteEntry = remoteEntry,
- )
- val sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
- compareByDescending { it.first.lastUsedTime }
+ isBiometricFlow = isBiometricFlow
)
return CreateCredentialUiState(
enabledProviders = enabledProviders,
@@ -430,12 +449,7 @@ class CreateFlowUtils {
currentScreenState = initialScreenState,
requestDisplayInfo = requestDisplayInfo,
sortedCreateOptionsPairs = sortedCreateOptionsPairs,
- activeEntry = toActiveEntry(
- defaultProvider = defaultProvider,
- sortedCreateOptionsPairs = sortedCreateOptionsPairs,
- remoteEntry = remoteEntry,
- remoteEntryProvider = remoteEntryProvider,
- ),
+ activeEntry = activeEntry,
remoteEntry = remoteEntry,
foundCandidateFromUserDefaultProvider = defaultProviderSetByUser != null,
)
@@ -444,9 +458,12 @@ class CreateFlowUtils {
fun toCreateScreenState(
createOptionSize: Int,
remoteEntry: RemoteInfo?,
+ isBiometricFlow: Boolean,
): CreateScreenState {
return if (createOptionSize == 0 && remoteEntry != null) {
CreateScreenState.EXTERNAL_ONLY_SELECTION
+ } else if (isBiometricFlow) {
+ CreateScreenState.BIOMETRIC_SELECTION
} else {
CreateScreenState.CREATION_OPTION_SELECTION
}
@@ -503,8 +520,8 @@ class CreateFlowUtils {
it.hasHint("androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_" +
"SELECT_ALLOWED")
}?.text == "true",
- // TODO(b/326243754) : Handle this when the create flow is added; for now the
- // create flow does not support biometric values
+ biometricRequest = retrieveEntryBiometricRequest(it,
+ CREATE_ENTRY_PREFIX),
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricFlowType.kt
index 5d2b0ad2b0a6..263cfd574d73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricFlowType.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.shared.model
+package com.android.credentialmanager.common
-/** An offset of view, used to adjust bounds. */
-data class ViewPosition(val left: Int = 0, val top: Int = 0)
+enum class BiometricFlowType {
+ GET,
+ CREATE
+} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index a30956ecf5a5..be3e0437a83e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -26,11 +26,14 @@ import androidx.core.content.ContextCompat.getMainExecutor
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.R
import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.getCreateTitleResCode
import com.android.credentialmanager.getflow.ProviderDisplayInfo
import com.android.credentialmanager.getflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.generateDisplayTitleTextResCode
import com.android.credentialmanager.model.BiometricRequestInfo
+import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.model.creation.CreateOptionInfo
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.model.get.ProviderInfo
import java.lang.Exception
@@ -39,14 +42,30 @@ import java.lang.Exception
* Aggregates common display information used for the Biometric Flow.
* Namely, this adds the ability to encapsulate the [providerIcon], the providers icon, the
* [providerName], which represents the name of the provider, the [displayTitleText] which is
- * the large text displaying the flow in progress, and the [descriptionAboveBiometricButton], which
+ * the large text displaying the flow in progress, and the [descriptionForCredential], which
* describes details of where the credential is being saved, and how.
+ * (E.g. assume a hypothetical provider 'Any Provider' for *passkey* flows with Your@Email.com:
+ *
+ * 'get' flow:
+ * - [providerIcon] and [providerName] = 'Any Provider' (and it's icon)
+ * - [displayTitleText] = "Use your saved passkey for Any Provider?"
+ * - [descriptionForCredential] = "Use your screen lock to sign in to Any Provider with
+ * Your@Email.com"
+ *
+ * 'create' flow:
+ * - [providerIcon] and [providerName] = 'Any Provider' (and it's icon)
+ * - [displayTitleText] = "Create passkey to sign in to Any Provider?"
+ * - [descriptionForCredential] = "Use your screen lock to create a passkey for Any Provider?"
+ * ).
+ *
+ * The above are examples; the credential type can change depending on scenario.
+ * // TODO(b/333445112) : Finalize once all the strings and create flow is iterated to completion
*/
data class BiometricDisplayInfo(
val providerIcon: Bitmap,
val providerName: String,
val displayTitleText: String,
- val descriptionAboveBiometricButton: String,
+ val descriptionForCredential: String,
val biometricRequestInfo: BiometricRequestInfo,
)
@@ -57,9 +76,7 @@ data class BiometricDisplayInfo(
*/
data class BiometricState(
val biometricResult: BiometricResult? = null,
- val biometricError: BiometricError? = null,
- val biometricHelp: BiometricHelp? = null,
- val biometricAcquireInfo: Int? = null,
+ val biometricStatus: BiometricPromptState = BiometricPromptState.INACTIVE
)
/**
@@ -88,56 +105,115 @@ data class BiometricHelp(
)
/**
- * This will handle the logic for integrating credential manager with the biometric prompt for the
- * single account biometric experience. This simultaneously handles both the get and create flows,
- * by retrieving all the data from credential manager, and properly parsing that data into the
- * biometric prompt.
+ * This is the entry point to start the integrated biometric prompt for 'get' flows. It captures
+ * information specific to the get flow, along with required shared callbacks and more general
+ * info across both flows, such as the tapped [EntryInfo] or [sendDataToProvider].
*/
-fun runBiometricFlow(
+fun runBiometricFlowForGet(
biometricEntry: EntryInfo,
context: Context,
openMoreOptionsPage: () -> Unit,
sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
onCancelFlowAndFinish: () -> Unit,
onIllegalStateAndFinish: (String) -> Unit,
+ getBiometricPromptState: () -> BiometricPromptState,
+ onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
+ onBiometricFailureFallback: (BiometricFlowType) -> Unit,
getRequestDisplayInfo: RequestDisplayInfo? = null,
getProviderInfoList: List<ProviderInfo>? = null,
getProviderDisplayInfo: ProviderDisplayInfo? = null,
- onBiometricFailureFallback: () -> Unit,
+) {
+ if (getBiometricPromptState() != BiometricPromptState.INACTIVE) {
+ // Screen is already up, do not re-launch
+ return
+ }
+ onBiometricPromptStateChange(BiometricPromptState.PENDING)
+ val biometricDisplayInfo = validateAndRetrieveBiometricGetDisplayInfo(
+ getRequestDisplayInfo,
+ getProviderInfoList,
+ getProviderDisplayInfo,
+ context, biometricEntry
+ )
+
+ if (biometricDisplayInfo == null) {
+ onBiometricFailureFallback(BiometricFlowType.GET)
+ return
+ }
+
+ val callback: BiometricPrompt.AuthenticationCallback =
+ setupBiometricAuthenticationCallback(sendDataToProvider, biometricEntry,
+ onCancelFlowAndFinish, onIllegalStateAndFinish, onBiometricPromptStateChange)
+
+ Log.d(TAG, "The BiometricPrompt API call begins.")
+ runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
+ onBiometricFailureFallback, BiometricFlowType.GET)
+}
+
+/**
+ * This is the entry point to start the integrated biometric prompt for 'create' flows. It captures
+ * information specific to the create flow, along with required shared callbacks and more general
+ * info across both flows, such as the tapped [EntryInfo] or [sendDataToProvider].
+ */
+fun runBiometricFlowForCreate(
+ biometricEntry: EntryInfo,
+ context: Context,
+ openMoreOptionsPage: () -> Unit,
+ sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+ onCancelFlowAndFinish: () -> Unit,
+ onIllegalStateAndFinish: (String) -> Unit,
+ getBiometricPromptState: () -> BiometricPromptState,
+ onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
+ onBiometricFailureFallback: (BiometricFlowType) -> Unit,
createRequestDisplayInfo: com.android.credentialmanager.createflow
.RequestDisplayInfo? = null,
createProviderInfo: EnabledProviderInfo? = null,
) {
- var biometricDisplayInfo: BiometricDisplayInfo? = null
- if (getRequestDisplayInfo != null) {
- biometricDisplayInfo = validateAndRetrieveBiometricGetDisplayInfo(getRequestDisplayInfo,
- getProviderInfoList,
- getProviderDisplayInfo,
- context, biometricEntry)
- } else if (createRequestDisplayInfo != null) {
- // TODO(b/326243754) : Create Flow to be implemented in follow up
- biometricDisplayInfo = validateBiometricCreateFlow(
- createRequestDisplayInfo,
- createProviderInfo
- )
+ if (getBiometricPromptState() != BiometricPromptState.INACTIVE) {
+ // Screen is already up, do not re-launch
+ return
}
+ onBiometricPromptStateChange(BiometricPromptState.PENDING)
+ val biometricDisplayInfo = validateAndRetrieveBiometricCreateDisplayInfo(
+ createRequestDisplayInfo,
+ createProviderInfo,
+ context, biometricEntry
+ )
if (biometricDisplayInfo == null) {
- onBiometricFailureFallback()
+ onBiometricFailureFallback(BiometricFlowType.CREATE)
return
}
- val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, openMoreOptionsPage,
- biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators)
-
val callback: BiometricPrompt.AuthenticationCallback =
setupBiometricAuthenticationCallback(sendDataToProvider, biometricEntry,
- onCancelFlowAndFinish, onIllegalStateAndFinish)
+ onCancelFlowAndFinish, onIllegalStateAndFinish, onBiometricPromptStateChange)
+
+ Log.d(TAG, "The BiometricPrompt API call begins.")
+ runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
+ onBiometricFailureFallback, BiometricFlowType.CREATE)
+}
+
+/**
+ * This will handle the logic for integrating credential manager with the biometric prompt for the
+ * single account biometric experience. This simultaneously handles both the get and create flows,
+ * by retrieving all the data from credential manager, and properly parsing that data into the
+ * biometric prompt.
+ */
+private fun runBiometricFlow(
+ context: Context,
+ biometricDisplayInfo: BiometricDisplayInfo,
+ callback: BiometricPrompt.AuthenticationCallback,
+ openMoreOptionsPage: () -> Unit,
+ onBiometricFailureFallback: (BiometricFlowType) -> Unit,
+ biometricFlowType: BiometricFlowType
+) {
+ val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, openMoreOptionsPage,
+ biometricDisplayInfo.biometricRequestInfo, biometricFlowType)
val cancellationSignal = CancellationSignal()
cancellationSignal.setOnCancelListener {
Log.d(TAG, "Your cancellation signal was called.")
- // TODO(b/326243754) : Migrate towards passing along the developer cancellation signal
+ // TODO(b/333445112) : Migrate towards passing along the developer cancellation signal
// or validate the necessity for this
}
@@ -147,30 +223,29 @@ fun runBiometricFlow(
biometricPrompt.authenticate(cancellationSignal, executor, callback)
} catch (e: IllegalArgumentException) {
Log.w(TAG, "Calling the biometric prompt API failed with: /n${e.localizedMessage}\n")
- onBiometricFailureFallback()
+ onBiometricFailureFallback(biometricFlowType)
}
}
/**
* Sets up the biometric prompt with the UI specific bits.
- * // TODO(b/326243754) : Pass in opId once dependency is confirmed via CryptoObject
- * // TODO(b/326243754) : Given fallbacks aren't allowed, for now we validate that device creds
- * // are NOT allowed to be passed in to avoid throwing an error. Later, however, once target
- * // alignments occur, we should add the bit back properly.
+ * // TODO(b/333445112) : Pass in opId once dependency is confirmed via CryptoObject
*/
private fun setupBiometricPrompt(
context: Context,
biometricDisplayInfo: BiometricDisplayInfo,
openMoreOptionsPage: () -> Unit,
- requestAllowedAuthenticators: Int,
+ biometricRequestInfo: BiometricRequestInfo,
+ biometricFlowType: BiometricFlowType,
): BiometricPrompt {
- val finalAuthenticators = removeDeviceCredential(requestAllowedAuthenticators)
+ val finalAuthenticators = removeDeviceCredential(biometricRequestInfo.allowedAuthenticators)
val biometricPrompt = BiometricPrompt.Builder(context)
.setTitle(biometricDisplayInfo.displayTitleText)
- // TODO(b/326243754) : Migrate to using new methods recently aligned upon
- .setNegativeButton(context.getString(R.string
- .dropdown_presentation_more_sign_in_options_text),
+ // TODO(b/333445112) : Migrate to using new methods and strings recently aligned upon
+ .setNegativeButton(context.getString(if (biometricFlowType == BiometricFlowType.GET)
+ R.string
+ .dropdown_presentation_more_sign_in_options_text else R.string.string_more_options),
getMainExecutor(context)) { _, _ ->
openMoreOptionsPage()
}
@@ -178,13 +253,13 @@ private fun setupBiometricPrompt(
.setConfirmationRequired(true)
.setLogoBitmap(biometricDisplayInfo.providerIcon)
.setLogoDescription(biometricDisplayInfo.providerName)
- .setDescription(biometricDisplayInfo.descriptionAboveBiometricButton)
+ .setDescription(biometricDisplayInfo.descriptionForCredential)
.build()
return biometricPrompt
}
-// TODO(b/326243754) : Remove after larger level alignments made on fallback negative button
+// TODO(b/333445112) : Remove after larger level alignments made on fallback negative button
// For the time being, we do not support the pin fallback until UX is decided.
private fun removeDeviceCredential(requestAllowedAuthenticators: Int): Int {
var finalAuthenticators = requestAllowedAuthenticators
@@ -214,16 +289,18 @@ private fun setupBiometricAuthenticationCallback(
selectedEntry: EntryInfo,
onCancelFlowAndFinish: () -> Unit,
onIllegalStateAndFinish: (String) -> Unit,
+ onBiometricPromptStateChange: (BiometricPromptState) -> Unit
): BiometricPrompt.AuthenticationCallback {
val callback: BiometricPrompt.AuthenticationCallback =
object : BiometricPrompt.AuthenticationCallback() {
- // TODO(b/326243754) : Validate remaining callbacks
+ // TODO(b/333445772) : Validate remaining callbacks
override fun onAuthenticationSucceeded(
authResult: BiometricPrompt.AuthenticationResult?
) {
super.onAuthenticationSucceeded(authResult)
try {
if (authResult != null) {
+ onBiometricPromptStateChange(BiometricPromptState.COMPLETE)
sendDataToProvider(selectedEntry, authResult)
} else {
onIllegalStateAndFinish("The biometric flow succeeded but unexpectedly " +
@@ -238,26 +315,24 @@ private fun setupBiometricAuthenticationCallback(
override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence?) {
super.onAuthenticationHelp(helpCode, helpString)
Log.d(TAG, "Authentication help discovered: $helpCode and $helpString")
- // TODO(b/326243754) : Decide on strategy with provider (a simple log probably
- // suffices here)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
super.onAuthenticationError(errorCode, errString)
Log.d(TAG, "Authentication error-ed out: $errorCode and $errString")
+ onBiometricPromptStateChange(BiometricPromptState.COMPLETE)
if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED) {
// Note that because the biometric prompt is imbued directly
// into the selector, parity applies to the selector's cancellation instead
// of the provider's biometric prompt cancellation.
onCancelFlowAndFinish()
}
- // TODO(b/326243754) : Propagate to provider
+ // TODO(b/333445772) : Propagate to provider
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
Log.d(TAG, "Authentication failed.")
- // TODO(b/326243754) : Propagate to provider
}
}
return callback
@@ -283,7 +358,7 @@ private fun validateAndRetrieveBiometricGetDisplayInfo(
if (getRequestDisplayInfo != null && getProviderInfoList != null &&
getProviderDisplayInfo != null) {
if (selectedEntry !is CredentialEntryInfo) { return null }
- return getBiometricDisplayValues(getProviderInfoList,
+ return retrieveBiometricGetDisplayValues(getProviderInfoList,
context, getRequestDisplayInfo, selectedEntry)
}
return null
@@ -292,16 +367,19 @@ private fun validateAndRetrieveBiometricGetDisplayInfo(
/**
* Creates the [BiometricDisplayInfo] for create flows, and early handles conditional
* checking between the two. The reason for this method matches the logic for the
- * [validateBiometricGetFlow] with the only difference being that this is for the create flow.
+ * [validateAndRetrieveBiometricGetDisplayInfo] with the only difference being that this is for
+ * the create flow.
*/
-private fun validateBiometricCreateFlow(
+private fun validateAndRetrieveBiometricCreateDisplayInfo(
createRequestDisplayInfo: com.android.credentialmanager.createflow.RequestDisplayInfo?,
createProviderInfo: EnabledProviderInfo?,
+ context: Context,
+ selectedEntry: EntryInfo,
): BiometricDisplayInfo? {
if (createRequestDisplayInfo != null && createProviderInfo != null) {
- } else if (createRequestDisplayInfo != null && createProviderInfo != null) {
- // TODO(b/326243754) : Create Flow to be implemented in follow up
- return createFlowDisplayValues()
+ if (selectedEntry !is CreateOptionInfo) { return null }
+ return retrieveBiometricCreateDisplayValues(createRequestDisplayInfo, createProviderInfo,
+ context, selectedEntry)
}
return null
}
@@ -312,16 +390,16 @@ private fun validateBiometricCreateFlow(
* to the original selector. Note that these redundant checks are just failsafe; the original
* flow should never reach here with invalid params.
*/
-private fun getBiometricDisplayValues(
+private fun retrieveBiometricGetDisplayValues(
getProviderInfoList: List<ProviderInfo>,
context: Context,
getRequestDisplayInfo: RequestDisplayInfo,
selectedEntry: CredentialEntryInfo,
): BiometricDisplayInfo? {
- var icon: Bitmap? = null
- var providerName: String? = null
- var displayTitleText: String? = null
- var descriptionText: String? = null
+ val icon: Bitmap?
+ val providerName: String?
+ val displayTitleText: String?
+ val descriptionText: String?
val primaryAccountsProviderInfo = retrievePrimaryAccountProviderInfo(selectedEntry.providerId,
getProviderInfoList)
icon = primaryAccountsProviderInfo?.icon?.toBitmap()
@@ -346,17 +424,47 @@ private fun getBiometricDisplayValues(
username
)
return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
- displayTitleText = displayTitleText, descriptionAboveBiometricButton = descriptionText,
+ displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
}
/**
- * Handles the biometric sign in via the 'create credentials' flow, or early validates this flow
- * needs to fallback.
+ * Handles the biometric sign in via the create credentials flow. Stricter in the get flow in that
+ * if this is called, a result is guaranteed. Specifically, this is guaranteed to return a non-null
+ * value unlike the get counterpart.
*/
-private fun createFlowDisplayValues(): BiometricDisplayInfo? {
- // TODO(b/326243754) : Create Flow to be implemented in follow up
- return null
+private fun retrieveBiometricCreateDisplayValues(
+ createRequestDisplayInfo: com.android.credentialmanager.createflow.RequestDisplayInfo,
+ createProviderInfo: EnabledProviderInfo,
+ context: Context,
+ selectedEntry: CreateOptionInfo,
+): BiometricDisplayInfo {
+ val icon: Bitmap?
+ val providerName: String?
+ val displayTitleText: String?
+ icon = createProviderInfo.icon.toBitmap()
+ providerName = createProviderInfo.displayName
+ displayTitleText = context.getString(
+ getCreateTitleResCode(createRequestDisplayInfo),
+ createRequestDisplayInfo.appName
+ )
+ val descriptionText: String = context.getString(
+ when (createRequestDisplayInfo.type) {
+ CredentialType.PASSKEY ->
+ R.string.choose_create_single_tap_passkey_title
+
+ CredentialType.PASSWORD ->
+ R.string.choose_create_single_tap_password_title
+
+ CredentialType.UNKNOWN ->
+ R.string.choose_create_single_tap_sign_in_title
+ },
+ createRequestDisplayInfo.appName,
+ )
+ // TODO(b/333445112) : Add a subtitle and any other recently aligned ideas
+ return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
+ displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
+ biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
}
/**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricPromptState.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricPromptState.kt
new file mode 100644
index 000000000000..e1aa0418e7a0
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricPromptState.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.common
+
+enum class BiometricPromptState {
+ /** The biometric prompt hasn't been activated. */
+ INACTIVE,
+ /** The biometric prompt is active but data hasn't been returned yet. */
+ PENDING,
+ /** The biometric prompt has closed and returned data we then send to the provider activity. */
+ COMPLETE
+} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index af78573ee9e9..122b8964dc96 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -17,6 +17,7 @@
package com.android.credentialmanager.createflow
import android.credentials.flags.Flags.selectorUiImprovementsEnabled
+import android.hardware.biometrics.BiometricPrompt
import android.text.TextUtils
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
@@ -26,7 +27,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.Divider
import androidx.compose.material.icons.Icons
@@ -38,6 +38,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -45,10 +46,13 @@ import androidx.core.graphics.drawable.toBitmap
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.R
+import com.android.credentialmanager.common.BiometricFlowType
+import com.android.credentialmanager.common.BiometricPromptState
import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
+import com.android.credentialmanager.common.runBiometricFlowForCreate
import com.android.credentialmanager.common.ui.ActionButton
import com.android.credentialmanager.common.ui.BodyMediumText
import com.android.credentialmanager.common.ui.BodySmallText
@@ -95,6 +99,26 @@ fun CreateCredentialScreen(
viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
onLog = { viewModel.logUiEvent(it) },
)
+ CreateScreenState.BIOMETRIC_SELECTION ->
+ BiometricSelectionPage(
+ biometricEntry = createCredentialUiState
+ .activeEntry?.activeEntryInfo,
+ onCancelFlowAndFinish = viewModel::onUserCancel,
+ onIllegalScreenStateAndFinish = viewModel::onIllegalUiState,
+ onMoreOptionSelected =
+ viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ enabledProviderInfo = createCredentialUiState
+ .activeEntry?.activeProvider!!,
+ onBiometricEntrySelected =
+ viewModel::createFlowOnEntrySelected,
+ fallbackToOriginalFlow =
+ viewModel::fallbackFromBiometricToNormalFlow,
+ getBiometricPromptState =
+ viewModel::getBiometricPromptState,
+ onBiometricPromptStateChange =
+ viewModel::onBiometricPromptStateChange
+ )
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderList = createCredentialUiState.enabledProviders,
@@ -313,20 +337,9 @@ fun CreationSelectionCard(
item { Divider(thickness = 16.dp, color = Color.Transparent) }
item {
HeadlineText(
- text = when (requestDisplayInfo.type) {
- CredentialType.PASSKEY -> stringResource(
- R.string.choose_create_option_passkey_title,
- requestDisplayInfo.appName
- )
- CredentialType.PASSWORD -> stringResource(
- R.string.choose_create_option_password_title,
- requestDisplayInfo.appName
- )
- CredentialType.UNKNOWN -> stringResource(
- R.string.choose_create_option_sign_in_title,
- requestDisplayInfo.appName
- )
- }
+ text = stringResource(
+ getCreateTitleResCode(requestDisplayInfo),
+ requestDisplayInfo.appName)
)
}
item { Divider(thickness = 24.dp, color = Color.Transparent) }
@@ -560,4 +573,36 @@ fun RemoteEntryRow(
iconImageVector = Icons.Outlined.QrCodeScanner,
entryHeadlineText = stringResource(R.string.another_device),
)
-} \ No newline at end of file
+}
+
+@Composable
+internal fun BiometricSelectionPage(
+ biometricEntry: EntryInfo?,
+ onMoreOptionSelected: () -> Unit,
+ requestDisplayInfo: RequestDisplayInfo,
+ enabledProviderInfo: EnabledProviderInfo,
+ onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+ onCancelFlowAndFinish: () -> Unit,
+ onIllegalScreenStateAndFinish: (String) -> Unit,
+ fallbackToOriginalFlow: (BiometricFlowType) -> Unit,
+ getBiometricPromptState: () -> BiometricPromptState,
+ onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
+) {
+ if (biometricEntry == null) {
+ fallbackToOriginalFlow(BiometricFlowType.CREATE)
+ return
+ }
+ runBiometricFlowForCreate(
+ biometricEntry = biometricEntry,
+ context = LocalContext.current,
+ openMoreOptionsPage = onMoreOptionSelected,
+ sendDataToProvider = onBiometricEntrySelected,
+ onCancelFlowAndFinish = onCancelFlowAndFinish,
+ getBiometricPromptState = getBiometricPromptState,
+ onBiometricPromptStateChange = onBiometricPromptStateChange,
+ createRequestDisplayInfo = requestDisplayInfo,
+ createProviderInfo = enabledProviderInfo,
+ onBiometricFailureFallback = fallbackToOriginalFlow,
+ onIllegalStateAndFinish = onIllegalScreenStateAndFinish,
+ )
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 617a981fc4ba..ddd4139b65b6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -16,9 +16,11 @@
package com.android.credentialmanager.createflow
+import android.credentials.flags.Flags.credmanBiometricApiEnabled
import android.graphics.drawable.Drawable
-import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.R
import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.creation.CreateOptionInfo
import com.android.credentialmanager.model.creation.RemoteInfo
@@ -33,14 +35,94 @@ data class CreateCredentialUiState(
val foundCandidateFromUserDefaultProvider: Boolean,
)
+/**
+ * Checks if this create flow is a biometric flow. Note that this flow differs slightly from the
+ * autoselect 'get' flow. Namely, given there can be multiple providers, rather than multiple
+ * accounts, the idea is that autoselect is ever only enabled for a single provider (or even, in
+ * that case, a single 'type' (family only, or work only) for a provider). However, for all other
+ * cases, the biometric screen should always show up if that entry contains the biometric bit.
+ */
+internal fun findBiometricFlowEntry(
+ activeEntry: ActiveEntry,
+ isAutoSelectFlow: Boolean,
+): CreateOptionInfo? {
+ if (!credmanBiometricApiEnabled()) {
+ return null
+ }
+ if (isAutoSelectFlow) {
+ // Since this is the create flow, auto select will only ever be true for a single provider.
+ // However, for all other cases, biometric should be used if that bit is opted into. If
+ // they clash, autoselect is always preferred, but that's only if there's a single provider.
+ return null
+ }
+ val biometricEntry = getCreateEntry(activeEntry)
+ return if (biometricEntry?.biometricRequest != null) biometricEntry else null
+}
+
+/**
+ * Retrieves the activeEntry by validating it is a [CreateOptionInfo]. This is done by ensuring
+ * that the [activeEntry] exists as a [CreateOptionInfo] to retrieve its [EntryInfo].
+ */
+internal fun getCreateEntry(
+ activeEntry: ActiveEntry?,
+): CreateOptionInfo? {
+ val entry = activeEntry?.activeEntryInfo
+ if (entry !is CreateOptionInfo) {
+ return null
+ }
+ return entry
+}
+
+/**
+* Determines if the flow is a biometric flow by taking into account autoselect criteria.
+*/
+internal fun isBiometricFlow(
+ activeEntry: ActiveEntry,
+ isAutoSelectFlow: Boolean,
+) = findBiometricFlowEntry(activeEntry, isAutoSelectFlow) != null
+
+/**
+ * This utility presents the correct resource string for the create flows title conditionally.
+ * Similar to generateDisplayTitleTextResCode in the 'get' flow, but for the create flow instead.
+ * This is for the title, and is a shared resource, unlike the specific unlock request text.
+ * E.g. this will look something like: "Create passkey to sign in to Tribank."
+ * // TODO(b/330396140) : Validate approach and add dynamic auth strings
+ */
+internal fun getCreateTitleResCode(createRequestDisplayInfo: RequestDisplayInfo): Int =
+ when (createRequestDisplayInfo.type) {
+ CredentialType.PASSKEY ->
+ R.string.choose_create_option_passkey_title
+
+ CredentialType.PASSWORD ->
+ R.string.choose_create_option_password_title
+
+ CredentialType.UNKNOWN ->
+ R.string.choose_create_option_sign_in_title
+ }
+
internal fun isFlowAutoSelectable(
uiState: CreateCredentialUiState
): Boolean {
- return uiState.requestDisplayInfo.isAutoSelectRequest &&
- uiState.sortedCreateOptionsPairs.size == 1 &&
- uiState.activeEntry?.activeEntryInfo?.let {
- it is CreateOptionInfo && it.allowAutoSelect
- } ?: false
+ return isFlowAutoSelectable(uiState.requestDisplayInfo, uiState.activeEntry,
+ uiState.sortedCreateOptionsPairs)
+}
+
+/**
+ * When initializing, the [CreateCredentialUiState] is generated after the initial screen is set.
+ * This overloaded method allows identifying if the flow is auto selectable prior to the creation
+ * of the [CreateCredentialUiState].
+ */
+internal fun isFlowAutoSelectable(
+ requestDisplayInfo: RequestDisplayInfo,
+ activeEntry: ActiveEntry?,
+ sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>
+): Boolean {
+ val isAutoSelectRequest = requestDisplayInfo.isAutoSelectRequest
+ if (sortedCreateOptionsPairs.size != 1) {
+ return false
+ }
+ val singleEntry = getCreateEntry(activeEntry)
+ return isAutoSelectRequest && singleEntry?.allowAutoSelect == true
}
internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean {
@@ -95,6 +177,7 @@ data class ActiveEntry (
/** The name of the current screen. */
enum class CreateScreenState {
CREATION_OPTION_SELECTION,
+ BIOMETRIC_SELECTION,
MORE_OPTIONS_SELECTION,
DEFAULT_PROVIDER_CONFIRMATION,
EXTERNAL_ONLY_SELECTION,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 4d7272c7716e..72b7814a791a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -52,9 +52,11 @@ import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.R
+import com.android.credentialmanager.common.BiometricFlowType
+import com.android.credentialmanager.common.BiometricPromptState
import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
-import com.android.credentialmanager.common.runBiometricFlow
+import com.android.credentialmanager.common.runBiometricFlowForGet
import com.android.credentialmanager.common.ui.ActionButton
import com.android.credentialmanager.common.ui.ActionEntry
import com.android.credentialmanager.common.ui.ConfirmButton
@@ -144,8 +146,6 @@ fun GetCredentialScreen(
} else if (credmanBiometricApiEnabled() && getCredentialUiState
.currentScreenState == GetScreenState.BIOMETRIC_SELECTION) {
BiometricSelectionPage(
- // TODO(b/326243754) : Utilize expected entry for this flow, confirm
- // activeEntry will always be what represents the single tap flow
biometricEntry = getCredentialUiState.activeEntry,
onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected,
onCancelFlowAndFinish = viewModel::onUserCancel,
@@ -156,7 +156,11 @@ fun GetCredentialScreen(
onBiometricEntrySelected =
viewModel::getFlowOnEntrySelected,
fallbackToOriginalFlow =
- viewModel::getFlowOnBackToPrimarySelectionScreen,
+ viewModel::fallbackFromBiometricToNormalFlow,
+ getBiometricPromptState =
+ viewModel::getBiometricPromptState,
+ onBiometricPromptStateChange =
+ viewModel::onBiometricPromptStateChange
)
} else {
AllSignInOptionCard(
@@ -220,19 +224,23 @@ internal fun BiometricSelectionPage(
providerInfoList: List<ProviderInfo>,
providerDisplayInfo: ProviderDisplayInfo,
onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult?) -> Unit,
- fallbackToOriginalFlow: () -> Unit,
+ fallbackToOriginalFlow: (BiometricFlowType) -> Unit,
+ getBiometricPromptState: () -> BiometricPromptState,
+ onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
) {
if (biometricEntry == null) {
- fallbackToOriginalFlow()
+ fallbackToOriginalFlow(BiometricFlowType.GET)
return
}
- runBiometricFlow(
+ runBiometricFlowForGet(
biometricEntry = biometricEntry,
context = LocalContext.current,
openMoreOptionsPage = onMoreOptionSelected,
sendDataToProvider = onBiometricEntrySelected,
onCancelFlowAndFinish = onCancelFlowAndFinish,
onIllegalStateAndFinish = onIllegalStateAndFinish,
+ getBiometricPromptState = getBiometricPromptState,
+ onBiometricPromptStateChange = onBiometricPromptStateChange,
getRequestDisplayInfo = requestDisplayInfo,
getProviderInfoList = providerInfoList,
getProviderDisplayInfo = providerDisplayInfo,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 6d5b52a7a5f9..b03407b9ebea 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -238,6 +238,7 @@ fun toProviderDisplayInfo(
/**
* This generates the res code for the large display title text for the selector. For example, it
* retrieves the resource for strings like: "Use your saved passkey for *rpName*".
+ * TODO(b/330396140) : Validate approach and add dynamic auth strings
*/
internal fun generateDisplayTitleTextResCode(
singleEntryType: CredentialType,
@@ -285,7 +286,7 @@ private fun toGetScreenState(
GetScreenState.REMOTE_ONLY
else if (isRequestForAllOptions)
GetScreenState.ALL_SIGN_IN_OPTIONS
- else if (isBiometricFlow(providerDisplayInfo))
+ else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo)))
GetScreenState.BIOMETRIC_SELECTION
else GetScreenState.PRIMARY_SELECTION
}
@@ -293,9 +294,14 @@ private fun toGetScreenState(
/**
* Determines if the flow is a biometric flow by taking into account autoselect criteria.
*/
-internal fun isBiometricFlow(providerDisplayInfo: ProviderDisplayInfo) =
- findBiometricFlowEntry(providerDisplayInfo,
- findAutoSelectEntry(providerDisplayInfo) != null) != null
+internal fun isBiometricFlow(providerDisplayInfo: ProviderDisplayInfo, isAutoSelectFlow: Boolean) =
+ findBiometricFlowEntry(providerDisplayInfo, isAutoSelectFlow) != null
+
+/**
+ * Determines if the flow is an autoselect flow.
+ */
+internal fun isFlowAutoSelectable(providerDisplayInfo: ProviderDisplayInfo) =
+ findAutoSelectEntry(providerDisplayInfo) != null
internal class CredentialEntryInfoComparatorByTypeThenTimestamp(
val typePriorityMap: Map<String, Int>,
diff --git a/packages/InputDevices/res/raw/keyboard_layout_german.kcm b/packages/InputDevices/res/raw/keyboard_layout_german.kcm
index fbb9bb68160b..1fb0924904f0 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_german.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_german.kcm
@@ -28,82 +28,93 @@ map key 86 PLUS # < > |
key GRAVE {
label: '^'
- base: '^'
+ base: '\u0302'
shift: '\u00b0'
}
key 1 {
label: '1'
base: '1'
- shift: '!'
+ shift, capslock: '!'
+ shift+capslock: '1'
}
key 2 {
label: '2'
base: '2'
- shift: '"'
+ shift, capslock: '"'
+ shift+capslock: '2'
ralt: '\u00b2'
}
key 3 {
label: '3'
base: '3'
- shift: '\u00a7'
+ shift, capslock: '\u00a7'
+ shift+capslock: '3'
ralt: '\u00b3'
}
key 4 {
label: '4'
base: '4'
- shift: '$'
+ shift, capslock: '$'
+ shift+capslock: '4'
}
key 5 {
label: '5'
base: '5'
- shift: '%'
+ shift, capslock: '%'
+ shift+capslock: '5'
}
key 6 {
label: '6'
base: '6'
- shift: '&'
+ shift, capslock: '&'
+ shift+capslock: '6'
}
key 7 {
label: '7'
base: '7'
- shift: '/'
+ shift, capslock: '/'
+ shift+capslock: '7'
ralt: '{'
}
key 8 {
label: '8'
base: '8'
- shift: '('
+ shift, capslock: '('
+ shift+capslock: '8'
ralt: '['
}
key 9 {
label: '9'
base: '9'
- shift: ')'
+ shift, capslock: ')'
+ shift+capslock: '9'
ralt: ']'
}
key 0 {
label: '0'
base: '0'
- shift: '='
+ shift, capslock: '='
+ shift+capslock: '0'
ralt: '}'
}
key SLASH {
label: '\u00df'
base: '\u00df'
- capslock: '\u1e9e'
- shift: '?'
+ shift, capslock: '?'
+ shift+capslock: '\u00df'
ralt: '\\'
+ shift+ralt: '\u1e9e'
}
key EQUALS {
@@ -196,7 +207,8 @@ key LEFT_BRACKET {
key RIGHT_BRACKET {
label: '+'
base: '+'
- shift: '*'
+ shift, capslock: '*'
+ shift+capslock: '+'
ralt: '~'
}
@@ -282,7 +294,8 @@ key APOSTROPHE {
key BACKSLASH {
label: '#'
base: '#'
- shift: '\''
+ shift, capslock: '\''
+ shift+capslock: '#'
}
### ROW 4
@@ -347,13 +360,15 @@ key M {
key COMMA {
label: ','
base: ','
- shift: ';'
+ shift, capslock: ';'
+ shift+capslock: ','
}
key PERIOD {
label: '.'
base: '.'
- shift: ':'
+ shift, capslock: ':'
+ shift+capslock: '.'
}
key MINUS {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_thai_kedmanee.kcm b/packages/InputDevices/res/raw/keyboard_layout_thai_kedmanee.kcm
new file mode 100644
index 000000000000..2283032e9450
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_thai_kedmanee.kcm
@@ -0,0 +1,321 @@
+# 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.
+
+#
+# Thai Kedmanee keyboard layout.
+#
+
+type OVERLAY
+
+map key 86 PLUS
+
+### ROW 1
+
+key GRAVE {
+ label: '_'
+ base: '_'
+ shift, capslock: '%'
+}
+
+key 1 {
+ label: '\u0e45'
+ base: '\u0e45'
+ shift, capslock: '+'
+}
+
+key 2 {
+ label: '/'
+ base: '/'
+ shift, capslock: '\u0e51'
+}
+
+key 3 {
+ label: '-'
+ base: '-'
+ shift, capslock: '\u0e52'
+}
+
+key 4 {
+ label: '\u0e20'
+ base: '\u0e20'
+ shift, capslock: '\u0e53'
+}
+
+key 5 {
+ label: '\u0e16'
+ base: '\u0e16'
+ shift, capslock: '\u0e54'
+}
+
+key 6 {
+ label: '\u0e38'
+ base: '\u0e38'
+ shift, capslock: '\u0e39'
+}
+
+key 7 {
+ label: '\u0e36'
+ base: '\u0e36'
+ shift, capslock: '\u0e3f'
+}
+
+key 8 {
+ label: '\u0e04'
+ base: '\u0e04'
+ shift, capslock: '\u0e55'
+}
+
+key 9 {
+ label: '\u0e15'
+ base: '\u0e15'
+ shift, capslock: '\u0e56'
+}
+
+key 0 {
+ label: '\u0e08'
+ base: '\u0e08'
+ shift, capslock: '\u0e57'
+}
+
+key MINUS {
+ label: '\u0e02'
+ base: '\u0e02'
+ shift, capslock: '\u0e58'
+}
+
+key EQUALS {
+ label: '\u0e0a'
+ base: '\u0e0a'
+ shift, capslock: '\u0e59'
+}
+
+### ROW 2
+
+key Q {
+ label: '\u0e46'
+ base: '\u0e46'
+ shift, capslock: '\u0e50'
+}
+
+key W {
+ label: '\u0e44'
+ base: '\u0e44'
+ shift, capslock: '\u0022'
+}
+
+key E {
+ label: '\u0e33'
+ base: '\u0e33'
+ shift, capslock: '\u0e0e'
+}
+
+key R {
+ label: '\u0e1e'
+ base: '\u0e1e'
+ shift, capslock: '\u0e11'
+}
+
+key T {
+ label: '\u0e30'
+ base: '\u0e30'
+ shift, capslock: '\u0e18'
+}
+
+key Y {
+ label: '\u0e31'
+ base: '\u0e31'
+ shift, capslock: '\u0e4d'
+}
+
+key U {
+ label: '\u0e35'
+ base: '\u0e35'
+ shift, capslock: '\u0e4a'
+}
+
+key I {
+ label: '\u0e23'
+ base: '\u0e23'
+ shift, capslock: '\u0e13'
+}
+
+key O {
+ label: '\u0e19'
+ base: '\u0e19'
+ shift, capslock: '\u0e2f'
+}
+
+key P {
+ label: '\u0e22'
+ base: '\u0e22'
+ shift, capslock: '\u0e0d'
+}
+
+key LEFT_BRACKET {
+ label: '\u0e1a'
+ base: '\u0e1a'
+ shift, capslock: '\u0e10'
+ ctrl: '%'
+}
+
+key RIGHT_BRACKET {
+ label: '\u0e25'
+ base: '\u0e25'
+ shift, capslock: ','
+ ctrl: '\u0e51'
+}
+
+### ROW 3
+
+key A {
+ label: '\u0e1f'
+ base: '\u0e1f'
+ shift, capslock: '\u0e24'
+}
+
+key S {
+ label: '\u0e2b'
+ base: '\u0e2b'
+ shift, capslock: '\u0e06'
+}
+
+key D {
+ label: '\u0e01'
+ base: '\u0e01'
+ shift, capslock: '\u0e0f'
+}
+
+key F {
+ label: '\u0e14'
+ base: '\u0e14'
+ shift, capslock: '\u0e42'
+}
+
+key G {
+ label: '\u0e40'
+ base: '\u0e40'
+ shift, capslock: '\u0e0c'
+}
+
+key H {
+ label: '\u0e49'
+ base: '\u0e49'
+ shift, capslock: '\u0e47'
+}
+
+key J {
+ label: '\u0e48'
+ base: '\u0e48'
+ shift, capslock: '\u0e4b'
+}
+
+key K {
+ label: '\u0e32'
+ base: '\u0e32'
+ shift, capslock: '\u0e29'
+}
+
+key L {
+ label: '\u0e2a'
+ base: '\u0e2a'
+ shift, capslock: '\u0e28'
+}
+
+key SEMICOLON {
+ label: '\u0e27'
+ base: '\u0e27'
+ shift, capslock: '\u0e0b'
+}
+
+key APOSTROPHE {
+ label: '\u0e07'
+ base: '\u0e07'
+ shift, capslock: '.'
+}
+
+key BACKSLASH {
+ label: '\u0e03'
+ base: '\u0e03'
+ shift, capslock: '\u0e05'
+ ctrl: '+'
+}
+
+### ROW 4
+
+key PLUS {
+ label: '\u0e03'
+ base: '\u0e03'
+ shift, capslock: '\u0e05'
+ ctrl: '\u0e52'
+}
+
+key Z {
+ label: '\u0e1c'
+ base: '\u0e1c'
+ shift, capslock: '('
+}
+
+key X {
+ label: '\u0e1b'
+ base: '\u0e1b'
+ shift, capslock: ')'
+}
+
+key C {
+ label: '\u0e41'
+ base: '\u0e41'
+ shift, capslock: '\u0e09'
+}
+
+key V {
+ label: '\u0e2d'
+ base: '\u0e2d'
+ shift, capslock: '\u0e2e'
+}
+
+key B {
+ label: '\u0e34'
+ base: '\u0e34'
+ shift, capslock: '\u0e3a'
+}
+
+key N {
+ label: '\u0e37'
+ base: '\u0e37'
+ shift, capslock: '\u0e4c'
+}
+
+key M {
+ label: '\u0e17'
+ base: '\u0e17'
+ shift, capslock: '?'
+}
+
+key COMMA {
+ label: '\u0e21'
+ base: '\u0e21'
+ shift, capslock: '\u0e12'
+}
+
+key PERIOD {
+ label: '\u0e43'
+ base: '\u0e43'
+ shift, capslock: '\u0e2c'
+}
+
+key SLASH {
+ label: '\u0e1d'
+ base: '\u0e1d'
+ shift, capslock: '\u0e26'
+} \ No newline at end of file
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index 1e1394096926..33a1d760f417 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -146,4 +146,7 @@
<!-- Georgian keyboard layout label. [CHAR LIMIT=35] -->
<string name="keyboard_layout_georgian">Georgian</string>
+
+ <!-- Thai (Kedmanee variant) keyboard layout label. [CHAR LIMIT=35] -->
+ <string name="keyboard_layout_thai_kedmanee">Thai (Kedmanee)</string>
</resources>
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index ee49b23b3860..4b7ea90eeb38 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -318,4 +318,11 @@
android:label="@string/keyboard_layout_georgian"
android:keyboardLayout="@raw/keyboard_layout_georgian"
android:keyboardLocale="ka-Geor" />
+
+ <keyboard-layout
+ android:name="keyboard_layout_thai_kedmanee"
+ android:label="@string/keyboard_layout_thai_kedmanee"
+ android:keyboardLayout="@raw/keyboard_layout_thai_kedmanee"
+ android:keyboardLocale="th-Thai"
+ android:keyboardLayoutType="extended" />
</keyboard-layouts>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 8f5d07c15b0b..407ab5f1729b 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -245,8 +245,7 @@ public class UninstallAlertDialogFragment extends DialogFragment implements
}
private static boolean isArchivingEnabled() {
- return android.content.pm.Flags.archiving()
- || SystemProperties.getBoolean("pm.archiving.enabled", false);
+ return android.content.pm.Flags.archiving();
}
private boolean isCloneProfile(UserHandle userHandle) {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 761bb7918afd..91bd7916b0ab 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -40,7 +40,7 @@ import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider
import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
-import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
+import com.android.settingslib.spa.gallery.scaffold.NonScrollablePagerPageProvider
import com.android.settingslib.spa.gallery.page.SliderPageProvider
import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider
@@ -48,10 +48,12 @@ import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
import com.android.settingslib.spa.gallery.preference.PreferencePageProvider
import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.scaffold.PagerMainPageProvider
import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider
import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
+import com.android.settingslib.spa.gallery.scaffold.ScrollablePagerPageProvider
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver
@@ -84,7 +86,9 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
ArgumentPageProvider,
SliderPageProvider,
SpinnerPageProvider,
- SettingsPagerPageProvider,
+ PagerMainPageProvider,
+ NonScrollablePagerPageProvider,
+ ScrollablePagerPageProvider,
FooterPageProvider,
IllustrationPageProvider,
CategoryPageProvider,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index 1f028d5e7bc9..654719d906a9 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -39,9 +39,9 @@ import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider
import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
-import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
import com.android.settingslib.spa.gallery.page.SliderPageProvider
import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
+import com.android.settingslib.spa.gallery.scaffold.PagerMainPageProvider
import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider
import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
@@ -63,7 +63,7 @@ object HomePageProvider : SettingsPageProvider {
SuwScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ PagerMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
FooterPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/NonScrollablePagerPageProvider.kt
index dc45e6da0bc1..029773fdf8df 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/NonScrollablePagerPageProvider.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.gallery.page
+package com.android.settingslib.spa.gallery.scaffold
import android.os.Bundle
import androidx.compose.foundation.layout.Box
@@ -33,24 +33,19 @@ import com.android.settingslib.spa.widget.scaffold.SettingsPager
import com.android.settingslib.spa.widget.scaffold.SettingsScaffold
import com.android.settingslib.spa.widget.ui.PlaceholderTitle
-private const val TITLE = "Sample SettingsPager"
+object NonScrollablePagerPageProvider : SettingsPageProvider {
+ override val name = "NonScrollablePager"
+ private const val TITLE = "Sample Non Scrollable SettingsPager"
-object SettingsPagerPageProvider : SettingsPageProvider {
- override val name = "SettingsPager"
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = createSettingsPage())
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
- }
+ fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = createSettingsPage())
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
- override fun getTitle(arguments: Bundle?): String {
- return TITLE
- }
+ override fun getTitle(arguments: Bundle?) = TITLE
@Composable
override fun Page(arguments: Bundle?) {
@@ -66,8 +61,8 @@ object SettingsPagerPageProvider : SettingsPageProvider {
@Preview(showBackground = true)
@Composable
-private fun SettingsPagerPagePreview() {
+private fun NonScrollablePagerPageProviderPreview() {
SettingsTheme {
- SettingsPagerPageProvider.Page(null)
+ NonScrollablePagerPageProvider.Page(null)
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt
new file mode 100644
index 000000000000..66cc38f74b07
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.settingslib.spa.gallery.scaffold
+
+import android.os.Bundle
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+
+object PagerMainPageProvider : SettingsPageProvider {
+ override val name = "PagerMain"
+ private val owner = createSettingsPage()
+ private const val TITLE = "Category: Pager"
+
+ override fun buildEntry(arguments: Bundle?) = listOf(
+ NonScrollablePagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ ScrollablePagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ )
+
+ fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+
+ override fun getTitle(arguments: Bundle?) = TITLE
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/ScrollablePagerPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/ScrollablePagerPageProvider.kt
new file mode 100644
index 000000000000..689a98a16b23
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/ScrollablePagerPageProvider.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.settingslib.spa.gallery.scaffold
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.SettingsPager
+import com.android.settingslib.spa.widget.scaffold.SettingsScaffold
+
+object ScrollablePagerPageProvider : SettingsPageProvider {
+ override val name = "ScrollablePager"
+ private const val TITLE = "Sample Scrollable SettingsPager"
+
+ fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = createSettingsPage())
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+
+ override fun getTitle(arguments: Bundle?) = TITLE
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ SettingsScaffold(title = getTitle(arguments)) { paddingValues ->
+ Box(Modifier.padding(paddingValues)) {
+ SettingsPager(listOf("Personal", "Work")) {
+ LazyColumn {
+ items(30) {
+ Preference(object : PreferenceModel {
+ override val title = it.toString()
+ })
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun ScrollablePagerPageProviderPreview() {
+ SettingsTheme {
+ ScrollablePagerPageProvider.Page(null)
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
index 6fc8de3ac49c..a0ab2ce6945d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
@@ -22,6 +22,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.SignalCellularAlt
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -37,6 +41,8 @@ import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.BottomAppBarButton
import com.android.settingslib.spa.widget.scaffold.SuwScaffold
import com.android.settingslib.spa.widget.ui.SettingsBody
+import com.android.settingslib.spa.widget.ui.Spinner
+import com.android.settingslib.spa.widget.ui.SpinnerOption
private const val TITLE = "Sample SuwScaffold"
@@ -67,13 +73,12 @@ private fun Page() {
actionButton = BottomAppBarButton("Next") {},
dismissButton = BottomAppBarButton("Cancel") {},
) {
- Column(Modifier.padding(SettingsDimension.itemPadding)) {
- SettingsBody("To add another SIM, download a new eSIM.")
- }
- Illustration(object : IllustrationModel {
- override val resId = R.drawable.accessibility_captioning_banner
- override val resourceType = ResourceType.IMAGE
- })
+ var selectedId by rememberSaveable { mutableIntStateOf(1) }
+ Spinner(
+ options = (1..3).map { SpinnerOption(id = it, text = "Option $it") },
+ selectedId = selectedId,
+ setId = { selectedId = it },
+ )
Column(Modifier.padding(SettingsDimension.itemPadding)) {
SettingsBody("To add another SIM, download a new eSIM.")
}
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 0ee9d595d875..85ad160f6d66 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
#
[versions]
-agp = "8.3.1"
+agp = "8.3.2"
compose-compiler = "1.5.11"
dexmaker-mockito = "2.28.3"
jvm = "17"
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.7-bin.zip
index 5c9634782bbe..7a9ac5afe013 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.7-bin.zip
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index 50ff9dff549b..182095e76e76 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,6 +16,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=gradle-8.6-bin.zip
+distributionUrl=gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
index 3e016f791962..75c8e6efd2e2 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
index d156f95afa79..06f0059ef547 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
index b8bb25fc9382..b72c8db9debc 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
index d156f95afa79..06f0059ef547 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png
index 90442080fe22..e43f27d49dee 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
index 36cbadc5bdaf..15c86dc67736 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
index a279481d2a5c..5be3a217ec91 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 2f2ac2467a6c..6344501ce789 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -65,7 +65,7 @@ dependencies {
api("androidx.lifecycle:lifecycle-runtime-compose")
api("androidx.navigation:navigation-compose:2.8.0-alpha05")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
- api("com.google.android.material:material:1.7.0-alpha03")
+ api("com.google.android.material:material:1.11.0")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
implementation("com.airbnb.android:lottie-compose:5.2.0")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
index 8b546b4de069..ef1a137198c2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
@@ -23,7 +23,6 @@ import com.android.settingslib.spa.widget.ui.SettingsSwitch
@Composable
fun TwoTargetSwitchPreference(
model: SwitchPreferenceModel,
- icon: @Composable (() -> Unit)? = null,
primaryEnabled: () -> Boolean = { true },
primaryOnClick: (() -> Unit)?,
) {
@@ -33,11 +32,12 @@ fun TwoTargetSwitchPreference(
summary = model.summary,
primaryEnabled = primaryEnabled,
primaryOnClick = primaryOnClick,
- icon = icon,
+ icon = model.icon,
) {
SettingsSwitch(
checked = model.checked(),
changeable = model.changeable,
+ contentDescription = model.title,
onCheckedChange = model.onCheckedChange,
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
index f372a45f9e59..163766a7a9c5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
@@ -19,7 +19,6 @@ package com.android.settingslib.spa.widget.scaffold
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
@@ -33,6 +32,8 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.movableContentOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import com.android.settingslib.spa.framework.theme.SettingsDimension
@@ -50,7 +51,7 @@ fun SuwScaffold(
title: String,
actionButton: BottomAppBarButton? = null,
dismissButton: BottomAppBarButton? = null,
- content: @Composable ColumnScope.() -> Unit,
+ content: @Composable () -> Unit,
) {
ActivityTitle(title)
Scaffold { innerPadding ->
@@ -59,6 +60,7 @@ fun SuwScaffold(
.padding(innerPadding)
.padding(top = SettingsDimension.itemPaddingAround)
) {
+ val movableContent = remember(content) { movableContentOf { content() } }
// Use single column layout in portrait, two columns in landscape.
val useSingleColumn = maxWidth < maxHeight
if (useSingleColumn) {
@@ -69,7 +71,7 @@ fun SuwScaffold(
.verticalScroll(rememberScrollState())
) {
Header(imageVector, title)
- content()
+ movableContent()
}
BottomBar(actionButton, dismissButton)
}
@@ -82,8 +84,9 @@ fun SuwScaffold(
Column(
Modifier
.weight(1f)
- .verticalScroll(rememberScrollState())) {
- content()
+ .verticalScroll(rememberScrollState())
+ ) {
+ movableContent()
}
}
BottomBar(actionButton, dismissButton)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
index a0da2418acbc..5155406b6d79 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
@@ -20,12 +20,16 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.Switch
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import com.android.settingslib.spa.framework.util.wrapOnSwitchWithLog
@Composable
internal fun SettingsSwitch(
checked: Boolean?,
changeable: () -> Boolean,
+ contentDescription: String? = null,
onCheckedChange: ((newChecked: Boolean) -> Unit)? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
@@ -33,6 +37,9 @@ internal fun SettingsSwitch(
Switch(
checked = checked,
onCheckedChange = wrapOnSwitchWithLog(onCheckedChange),
+ modifier = if (contentDescription != null) Modifier.semantics {
+ this.contentDescription = contentDescription
+ } else Modifier,
enabled = changeable(),
interactionSource = interactionSource,
)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index a395266ba5f9..e1e1ee5a8feb 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -151,7 +151,7 @@ class AppListRepositoryImpl(
}
private fun isArchivingEnabled(featureFlags: FeatureFlags) =
- featureFlags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false)
+ featureFlags.archiving()
override fun showSystemPredicate(
userIdFlow: Flow<Int>,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
index 5c2d7701fd6f..1f7122e82c30 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
@@ -33,11 +33,13 @@ fun <T : AppRecord> AppListItemModel<T>.AppListTwoTargetSwitchItem(
model = object : SwitchPreferenceModel {
override val title = label
override val summary = this@AppListTwoTargetSwitchItem.summary
+ override val icon = @Composable {
+ AppIcon(record.app, SettingsDimension.appIconItemSize)
+ }
override val checked = checked
override val changeable = changeable
override val onCheckedChange = onCheckedChange
},
- icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
primaryOnClick = onClick,
)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
index 87cd2b844a2b..c9934adfad22 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -52,6 +52,8 @@ internal class RestrictedSwitchPreferenceModel(
checked = model.checked,
)
+ override val icon = model.icon
+
override val checked = when (restrictedMode) {
null -> ({ null })
is NoRestricted -> model.checked
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
index e100773b2358..1bed73365e80 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
@@ -29,14 +29,12 @@ import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitc
@Composable
fun RestrictedTwoTargetSwitchPreference(
model: SwitchPreferenceModel,
- icon: @Composable (() -> Unit)? = null,
restrictions: Restrictions,
primaryEnabled: () -> Boolean = { true },
primaryOnClick: (() -> Unit)?,
) {
RestrictedTwoTargetSwitchPreference(
model = model,
- icon = icon,
primaryEnabled = primaryEnabled,
primaryOnClick = primaryOnClick,
restrictions = restrictions,
@@ -48,21 +46,19 @@ fun RestrictedTwoTargetSwitchPreference(
@Composable
internal fun RestrictedTwoTargetSwitchPreference(
model: SwitchPreferenceModel,
- icon: @Composable (() -> Unit)? = null,
primaryEnabled: () -> Boolean = { true },
primaryOnClick: (() -> Unit)?,
restrictions: Restrictions,
restrictionsProviderFactory: RestrictionsProviderFactory,
) {
if (restrictions.isEmpty()) {
- TwoTargetSwitchPreference(model, icon, primaryEnabled, primaryOnClick)
+ TwoTargetSwitchPreference(model, primaryEnabled, primaryOnClick)
return
}
val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
RestrictedSwitchWrapper(model, restrictedMode) { restrictedModel ->
TwoTargetSwitchPreference(
model = restrictedModel,
- icon = icon,
primaryEnabled = restrictedMode.restrictEnabled(primaryEnabled),
primaryOnClick = restrictedMode.restrictOnClick(primaryOnClick),
)
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index a4089c0d8697..7f4bebcf4a62 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -52,16 +52,12 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
-/**
- * Description of a single dashboard tile that the user can select.
- */
+/** Description of a single dashboard tile that the user can select. */
public abstract class Tile implements Parcelable {
private static final String TAG = "Tile";
- /**
- * Optional list of user handles which the intent should be launched on.
- */
+ /** Optional list of user handles which the intent should be launched on. */
public ArrayList<UserHandle> userHandle = new ArrayList<>();
public HashMap<UserHandle, PendingIntent> pendingIntentMap = new HashMap<>();
@@ -76,6 +72,7 @@ public abstract class Tile implements Parcelable {
private CharSequence mSummaryOverride;
private Bundle mMetaData;
private String mCategory;
+ private String mGroupKey;
public Tile(ComponentInfo info, String category, Bundle metaData) {
mComponentInfo = info;
@@ -83,6 +80,9 @@ public abstract class Tile implements Parcelable {
mComponentName = mComponentInfo.name;
mCategory = category;
mMetaData = metaData;
+ if (mMetaData != null) {
+ mGroupKey = metaData.getString(META_DATA_PREFERENCE_GROUP_KEY);
+ }
mIntent = new Intent().setClassName(mComponentPackage, mComponentName);
if (isNewTask()) {
mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -102,6 +102,7 @@ public abstract class Tile implements Parcelable {
if (isNewTask()) {
mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
+ mGroupKey = in.readString();
}
@Override
@@ -121,16 +122,13 @@ public abstract class Tile implements Parcelable {
}
dest.writeString(mCategory);
dest.writeBundle(mMetaData);
+ dest.writeString(mGroupKey);
}
- /**
- * Unique ID of the tile
- */
+ /** Unique ID of the tile */
public abstract int getId();
- /**
- * Human-readable description of the tile
- */
+ /** Human-readable description of the tile */
public abstract String getDescription();
protected abstract ComponentInfo getComponentInfo(Context context);
@@ -147,16 +145,12 @@ public abstract class Tile implements Parcelable {
return mComponentName;
}
- /**
- * Intent to launch when the preference is selected.
- */
+ /** Intent to launch when the preference is selected. */
public Intent getIntent() {
return mIntent;
}
- /**
- * Category in which the tile should be placed.
- */
+ /** Category in which the tile should be placed. */
public String getCategory() {
return mCategory;
}
@@ -165,9 +159,7 @@ public abstract class Tile implements Parcelable {
mCategory = newCategoryKey;
}
- /**
- * Priority of this tile, used for display ordering.
- */
+ /** Priority of this tile, used for display ordering. */
public int getOrder() {
if (hasOrder()) {
return mMetaData.getInt(META_DATA_KEY_ORDER);
@@ -176,32 +168,24 @@ public abstract class Tile implements Parcelable {
}
}
- /**
- * Check whether tile has order.
- */
+ /** Check whether tile has order. */
public boolean hasOrder() {
return mMetaData != null
&& mMetaData.containsKey(META_DATA_KEY_ORDER)
&& mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer;
}
- /**
- * Check whether tile has a switch.
- */
+ /** Check whether tile has a switch. */
public boolean hasSwitch() {
return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_SWITCH_URI);
}
- /**
- * Check whether tile has a pending intent.
- */
+ /** Check whether tile has a pending intent. */
public boolean hasPendingIntent() {
return !pendingIntentMap.isEmpty();
}
- /**
- * Title of the tile that is shown to the user.
- */
+ /** Title of the tile that is shown to the user. */
public CharSequence getTitle(Context context) {
CharSequence title = null;
ensureMetadataNotStale(context);
@@ -238,9 +222,7 @@ public abstract class Tile implements Parcelable {
mSummaryOverride = summaryOverride;
}
- /**
- * Optional summary describing what this tile controls.
- */
+ /** Optional summary describing what this tile controls. */
public CharSequence getSummary(Context context) {
if (mSummaryOverride != null) {
return mSummaryOverride;
@@ -275,16 +257,12 @@ public abstract class Tile implements Parcelable {
mMetaData = metaData;
}
- /**
- * The metaData from the activity that defines this tile.
- */
+ /** The metaData from the activity that defines this tile. */
public Bundle getMetaData() {
return mMetaData;
}
- /**
- * Optional key to use for this tile.
- */
+ /** Optional key to use for this tile. */
public String getKey(Context context) {
ensureMetadataNotStale(context);
if (!hasKey()) {
@@ -297,9 +275,7 @@ public abstract class Tile implements Parcelable {
}
}
- /**
- * Check whether title has key.
- */
+ /** Check whether title has key. */
public boolean hasKey() {
return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_KEYHINT);
}
@@ -325,8 +301,9 @@ public abstract class Tile implements Parcelable {
if (iconResId != 0 && iconResId != android.R.color.transparent) {
final Icon icon = Icon.createWithResource(componentInfo.packageName, iconResId);
if (isIconTintable(context)) {
- final TypedArray a = context.obtainStyledAttributes(new int[]{
- android.R.attr.colorControlNormal});
+ final TypedArray a =
+ context.obtainStyledAttributes(
+ new int[] {android.R.attr.colorControlNormal});
final int tintColor = a.getColor(0, 0);
a.recycle();
icon.setTint(tintColor);
@@ -349,26 +326,22 @@ public abstract class Tile implements Parcelable {
return false;
}
- /**
- * Whether the {@link Activity} should be launched in a separate task.
- */
+ /** Whether the {@link Activity} should be launched in a separate task. */
public boolean isNewTask() {
- if (mMetaData != null
- && mMetaData.containsKey(META_DATA_NEW_TASK)) {
+ if (mMetaData != null && mMetaData.containsKey(META_DATA_NEW_TASK)) {
return mMetaData.getBoolean(META_DATA_NEW_TASK);
}
return false;
}
- /**
- * Ensures metadata is not stale for this tile.
- */
+ /** Ensures metadata is not stale for this tile. */
private void ensureMetadataNotStale(Context context) {
final PackageManager pm = context.getApplicationContext().getPackageManager();
try {
- final long lastUpdateTime = pm.getPackageInfo(mComponentPackage,
- PackageManager.GET_META_DATA).lastUpdateTime;
+ final long lastUpdateTime =
+ pm.getPackageInfo(mComponentPackage, PackageManager.GET_META_DATA)
+ .lastUpdateTime;
if (lastUpdateTime == mLastUpdateTime) {
// All good. Do nothing
return;
@@ -382,72 +355,60 @@ public abstract class Tile implements Parcelable {
}
}
- public static final Creator<Tile> CREATOR = new Creator<Tile>() {
- public Tile createFromParcel(Parcel source) {
- final boolean isProviderTile = source.readBoolean();
- // reset the Parcel pointer before delegating to the real constructor.
- source.setDataPosition(0);
- return isProviderTile ? new ProviderTile(source) : new ActivityTile(source);
- }
+ public static final Creator<Tile> CREATOR =
+ new Creator<Tile>() {
+ public Tile createFromParcel(Parcel source) {
+ final boolean isProviderTile = source.readBoolean();
+ // reset the Parcel pointer before delegating to the real constructor.
+ source.setDataPosition(0);
+ return isProviderTile ? new ProviderTile(source) : new ActivityTile(source);
+ }
- public Tile[] newArray(int size) {
- return new Tile[size];
- }
- };
+ public Tile[] newArray(int size) {
+ return new Tile[size];
+ }
+ };
- /**
- * Check whether tile only has primary profile.
- */
+ /** Check whether tile only has primary profile. */
public boolean isPrimaryProfileOnly() {
return isPrimaryProfileOnly(mMetaData);
}
static boolean isPrimaryProfileOnly(Bundle metaData) {
- String profile = metaData != null
- ? metaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL;
+ String profile = metaData != null ? metaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL;
profile = (profile != null ? profile : PROFILE_ALL);
return TextUtils.equals(profile, PROFILE_PRIMARY);
}
- /**
- * Returns whether the tile belongs to another group / category.
- */
+ /** Returns whether the tile belongs to another group / category. */
public boolean hasGroupKey() {
- return mMetaData != null
- && !TextUtils.isEmpty(mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY));
+ return !TextUtils.isEmpty(mGroupKey);
}
- /**
- * Returns the group / category key this tile belongs to.
- */
+ /** Set the group / PreferenceCategory key this tile belongs to. */
+ public void setGroupKey(String groupKey) {
+ mGroupKey = groupKey;
+ }
+
+ /** Returns the group / category key this tile belongs to. */
public String getGroupKey() {
- return (mMetaData == null) ? null : mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY);
+ return mGroupKey;
}
- /**
- * Returns if this is searchable.
- */
+ /** Returns if this is searchable. */
public boolean isSearchable() {
return mMetaData == null || mMetaData.getBoolean(META_DATA_PREFERENCE_SEARCHABLE, true);
}
- /**
- * The type of the tile.
- */
+ /** The type of the tile. */
public enum Type {
- /**
- * A preference that can be tapped on to open a new page.
- */
+ /** A preference that can be tapped on to open a new page. */
ACTION,
- /**
- * A preference that can be tapped on to open an external app.
- */
+ /** A preference that can be tapped on to open an external app. */
EXTERNAL_ACTION,
- /**
- * A preference that shows an on / off switch that can be toggled by the user.
- */
+ /** A preference that shows an on / off switch that can be toggled by the user. */
SWITCH,
/**
@@ -460,7 +421,7 @@ public abstract class Tile implements Parcelable {
* A preference category with a title that can be used to group multiple preferences
* together.
*/
- GROUP;
+ GROUP
}
/**
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index d0929e19bc9b..b949cd58a862 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -77,9 +77,7 @@ public class TileUtils {
*/
public static final String IA_SETTINGS_ACTION = "com.android.settings.action.IA_SETTINGS";
- /**
- * Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities.
- */
+ /** Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities. */
private static final String SETTINGS_ACTION = "com.android.settings.action.SETTINGS";
private static final String OPERATOR_SETTINGS =
@@ -101,9 +99,7 @@ public class TileUtils {
*/
static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
- /**
- * The key used to get the package name of the icon resource for the preference.
- */
+ /** The key used to get the package name of the icon resource for the preference. */
static final String EXTRA_PREFERENCE_ICON_PACKAGE = "com.android.settings.icon_package";
/**
@@ -145,18 +141,17 @@ public class TileUtils {
"com.android.settings.bg.argb";
/**
- * Name of the meta-data item that should be set in the AndroidManifest.xml
- * to specify the content provider providing the icon that should be displayed for
- * the preference.
+ * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the
+ * content provider providing the icon that should be displayed for the preference.
*
- * Icon provided by the content provider overrides any static icon.
+ * <p>Icon provided by the content provider overrides any static icon.
*/
public static final String META_DATA_PREFERENCE_ICON_URI = "com.android.settings.icon_uri";
/**
- * Name of the meta-data item that should be set in the AndroidManifest.xml
- * to specify whether the icon is tintable. This should be a boolean value {@code true} or
- * {@code false}, set using {@code android:value}
+ * Name of the meta-data item that should be set in the AndroidManifest.xml to specify whether
+ * the icon is tintable. This should be a boolean value {@code true} or {@code false}, set using
+ * {@code android:value}
*/
public static final String META_DATA_PREFERENCE_ICON_TINTABLE =
"com.android.settings.icon_tintable";
@@ -171,41 +166,36 @@ public class TileUtils {
public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
/**
- * Name of the meta-data item that should be set in the AndroidManifest.xml
- * to specify the content provider providing the title text that should be displayed for the
- * preference.
+ * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the
+ * content provider providing the title text that should be displayed for the preference.
*
- * Title provided by the content provider overrides any static title.
+ * <p>Title provided by the content provider overrides any static title.
*/
- public static final String META_DATA_PREFERENCE_TITLE_URI =
- "com.android.settings.title_uri";
+ public static final String META_DATA_PREFERENCE_TITLE_URI = "com.android.settings.title_uri";
/**
- * Name of the meta-data item that should be set in the AndroidManifest.xml
- * to specify the summary text that should be displayed for the preference.
+ * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the
+ * summary text that should be displayed for the preference.
*/
public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
/**
- * Name of the meta-data item that should be set in the AndroidManifest.xml
- * to specify the content provider providing the summary text that should be displayed for the
- * preference.
+ * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the
+ * content provider providing the summary text that should be displayed for the preference.
*
- * Summary provided by the content provider overrides any static summary.
+ * <p>Summary provided by the content provider overrides any static summary.
*/
public static final String META_DATA_PREFERENCE_SUMMARY_URI =
"com.android.settings.summary_uri";
/**
- * Name of the meta-data item that should be set in the AndroidManifest.xml
- * to specify the content provider providing the switch that should be displayed for the
- * preference.
+ * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the
+ * content provider providing the switch that should be displayed for the preference.
*
- * This works with {@link #META_DATA_PREFERENCE_KEYHINT} which should also be set in the
+ * <p>This works with {@link #META_DATA_PREFERENCE_KEYHINT} which should also be set in the
* AndroidManifest.xml
*/
- public static final String META_DATA_PREFERENCE_SWITCH_URI =
- "com.android.settings.switch_uri";
+ public static final String META_DATA_PREFERENCE_SWITCH_URI = "com.android.settings.switch_uri";
/**
* Name of the meta-data item that can be set from the content provider providing the intent
@@ -215,8 +205,8 @@ public class TileUtils {
"com.android.settings.pending_intent";
/**
- * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile,
- * the app will always be run in the primary profile.
+ * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile, the app will
+ * always be run in the primary profile.
*
* @see #META_DATA_KEY_PROFILE
*/
@@ -231,11 +221,11 @@ public class TileUtils {
public static final String PROFILE_ALL = "all_profiles";
/**
- * Name of the meta-data item that should be set in the AndroidManifest.xml
- * to specify the profile in which the app should be run when the device has a managed profile.
- * The default value is {@link #PROFILE_ALL} which means the user will be presented with a
- * dialog to choose the profile. If set to {@link #PROFILE_PRIMARY} the app will always be
- * run in the primary profile.
+ * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the
+ * profile in which the app should be run when the device has a managed profile. The default
+ * value is {@link #PROFILE_ALL} which means the user will be presented with a dialog to choose
+ * the profile. If set to {@link #PROFILE_PRIMARY} the app will always be run in the primary
+ * profile.
*
* @see #PROFILE_PRIMARY
* @see #PROFILE_ALL
@@ -243,20 +233,16 @@ public class TileUtils {
public static final String META_DATA_KEY_PROFILE = "com.android.settings.profile";
/**
- * Name of the meta-data item that should be set in the AndroidManifest.xml
- * to specify whether the {@link android.app.Activity} should be launched in a separate task.
- * This should be a boolean value {@code true} or {@code false}, set using {@code android:value}
+ * Name of the meta-data item that should be set in the AndroidManifest.xml to specify whether
+ * the {@link android.app.Activity} should be launched in a separate task. This should be a
+ * boolean value {@code true} or {@code false}, set using {@code android:value}
*/
public static final String META_DATA_NEW_TASK = "com.android.settings.new_task";
- /**
- * If the entry should be shown in settings search results. Defaults to true.
- */
+ /** If the entry should be shown in settings search results. Defaults to true. */
public static final String META_DATA_PREFERENCE_SEARCHABLE = "com.android.settings.searchable";
- /**
- * Build a list of DashboardCategory.
- */
+ /** Build a list of DashboardCategory. */
public static List<DashboardCategory> getCategories(Context context,
Map<Pair<String, String>, Tile> cache) {
final long startTime = System.currentTimeMillis();
@@ -341,8 +327,8 @@ public class TileUtils {
UserHandle user, Map<Pair<String, String>, Tile> addedCache,
String defaultCategory, List<Tile> outTiles, Intent intent) {
final PackageManager pm = context.getPackageManager();
- final List<ResolveInfo> results = pm.queryIntentContentProvidersAsUser(intent,
- 0 /* flags */, user.getIdentifier());
+ final List<ResolveInfo> results =
+ pm.queryIntentContentProvidersAsUser(intent, 0 /* flags */, user.getIdentifier());
for (ResolveInfo resolved : results) {
if (!resolved.system) {
// Do not allow any app to add to settings, only system ones.
@@ -403,6 +389,8 @@ public class TileUtils {
tile.setMetaData(metaData);
}
+ tile.setGroupKey(metaData.getString(META_DATA_PREFERENCE_GROUP_KEY));
+
if (!tile.userHandle.contains(user)) {
tile.userHandle.add(user);
}
@@ -448,15 +436,15 @@ public class TileUtils {
/**
* Returns the complete uri from the meta data key of the tile.
*
- * A complete uri should contain at least one path segment and be one of the following types:
- * content://authority/method
- * content://authority/method/key
+ * <p>A complete uri should contain at least one path segment and be one of the following types:
+ * <br>content://authority/method
+ * <br>content://authority/method/key
*
- * If the uri from the tile is not complete, build a uri by the default method and the
+ * <p>If the uri from the tile is not complete, build a uri by the default method and the
* preference key.
*
- * @param tile Tile which contains meta data
- * @param metaDataKey Key mapping to the uri in meta data
+ * @param tile Tile which contains meta data
+ * @param metaDataKey Key mapping to the uri in meta data
* @param defaultMethod Method to be attached to the uri by default if it has no path segment
* @return Uri associated with the key
*/
@@ -501,9 +489,9 @@ public class TileUtils {
/**
* Gets the icon package name and resource id from content provider.
*
- * @param context context
+ * @param context context
* @param packageName package name of the target activity
- * @param uri URI for the content provider
+ * @param uri URI for the content provider
* @param providerMap Maps URI authorities to providers
* @return package name and resource id of the icon specified
*/
@@ -532,10 +520,10 @@ public class TileUtils {
/**
* Gets text associated with the input key from the content provider.
*
- * @param context context
- * @param uri URI for the content provider
+ * @param context context
+ * @param uri URI for the content provider
* @param providerMap Maps URI authorities to providers
- * @param key Key mapping to the text in bundle returned by the content provider
+ * @param key Key mapping to the text in bundle returned by the content provider
* @return Text associated with the key, if returned by the content provider
*/
public static String getTextFromUri(Context context, Uri uri,
@@ -547,10 +535,10 @@ public class TileUtils {
/**
* Gets boolean associated with the input key from the content provider.
*
- * @param context context
- * @param uri URI for the content provider
+ * @param context context
+ * @param uri URI for the content provider
* @param providerMap Maps URI authorities to providers
- * @param key Key mapping to the text in bundle returned by the content provider
+ * @param key Key mapping to the text in bundle returned by the content provider
* @return Boolean associated with the key, if returned by the content provider
*/
public static boolean getBooleanFromUri(Context context, Uri uri,
@@ -562,11 +550,11 @@ public class TileUtils {
/**
* Puts boolean associated with the input key to the content provider.
*
- * @param context context
- * @param uri URI for the content provider
+ * @param context context
+ * @param uri URI for the content provider
* @param providerMap Maps URI authorities to providers
- * @param key Key mapping to the text in bundle returned by the content provider
- * @param value Boolean associated with the key
+ * @param key Key mapping to the text in bundle returned by the content provider
+ * @param value Boolean associated with the key
* @return Bundle associated with the action, if returned by the content provider
*/
public static Bundle putBooleanToUriAndGetResult(Context context, Uri uri,
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index e09ab0086451..89f54d9b3b3b 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -36,14 +36,14 @@ flag {
name: "enable_le_audio_sharing"
namespace: "pixel_cross_device_control"
description: "Gates whether to enable LE audio sharing"
- bug: "305620450"
+ bug: "323125723"
}
flag {
name: "enable_le_audio_qr_code_private_broadcast_sharing"
namespace: "pixel_cross_device_control"
description: "Gates whether to enable LE audio private broadcast sharing via QR code"
- bug: "308368124"
+ bug: "323125723"
}
flag {
@@ -52,3 +52,13 @@ flag {
description: "Hide exclusively managed Bluetooth devices in BT settings menu."
bug: "324475542"
}
+
+flag {
+ name: "enable_set_preferred_transport_for_le_audio_device"
+ namespace: "bluetooth"
+ description: "Enable setting preferred transport for Le Audio device"
+ bug: "330581926"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
index 89d6ac39598b..3ed17247a5e3 100644
--- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
+++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
@@ -53,7 +53,7 @@
<EditText
android:id="@+id/user_name"
android:layout_width="match_parent"
- android:layout_height="@dimen/user_name_height_in_user_info_dialog"
+ android:layout_height="wrap_content"
android:layout_gravity="center"
android:minWidth="200dp"
android:layout_marginStart="6dp"
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 2bd4d023cd72..ab049042b5f9 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -82,7 +82,6 @@
<dimen name="add_a_photo_circled_padding">6dp</dimen>
<dimen name="user_photo_size_in_user_info_dialog">112dp</dimen>
<dimen name="add_a_photo_icon_size_in_user_info_dialog">32dp</dimen>
- <dimen name="user_name_height_in_user_info_dialog">48sp</dimen>
<!-- Minimum increment between density scales. -->
<fraction name="display_density_min_scale_interval">9%</fraction>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 56118dae3f96..06c41cb7cbc7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1993,6 +1993,40 @@ public class ApplicationsState {
};
/**
+ * Displays a combined list with "downloaded" and "visible in launcher" apps which belong to a
+ * user which is either not in quiet mode or allows showing apps even when in quiet mode.
+ */
+ public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET = new AppFilter() {
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(@NonNull AppEntry entry) {
+ if (entry.hideInQuietMode) {
+ return false;
+ }
+ if (AppUtils.isInstant(entry.info)) {
+ return false;
+ } else if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) {
+ return true;
+ } else if (!hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM)) {
+ return true;
+ } else if (entry.hasLauncherEntry) {
+ return true;
+ } else if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM) && entry.isHomeApp) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void refreshAppEntryOnRebuild(@NonNull AppEntry appEntry, boolean hideInQuietMode) {
+ appEntry.hideInQuietMode = hideInQuietMode;
+ }
+ };
+
+ /**
* Displays a combined list with "downloaded" and "visible in launcher" apps only.
*/
public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT = new AppFilter() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 57fcc7462a65..a906875e11ef 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -3,10 +3,13 @@ package com.android.settingslib.bluetooth;
import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED;
import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -30,6 +33,7 @@ import androidx.annotation.WorkerThread;
import androidx.core.graphics.drawable.IconCompat;
import com.android.settingslib.R;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.settingslib.widget.AdaptiveOutlineDrawable;
@@ -46,14 +50,14 @@ public class BluetoothUtils {
private static final String TAG = "BluetoothUtils";
public static final boolean V = false; // verbose logging
- public static final boolean D = true; // regular logging
+ public static final boolean D = true; // regular logging
public static final int META_INT_ERROR = -1;
public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
- private static final Set<String> EXCLUSIVE_MANAGERS = ImmutableSet.of(
- "com.google.android.gms.dck");
+ private static final Set<String> EXCLUSIVE_MANAGERS =
+ ImmutableSet.of("com.google.android.gms.dck");
private static ErrorListener sErrorListener;
@@ -89,23 +93,23 @@ public class BluetoothUtils {
/**
* @param context to access resources from
* @param cachedDevice to get class from
- * @return pair containing the drawable and the description of the Bluetooth class
- * of the device.
+ * @return pair containing the drawable and the description of the Bluetooth class of the
+ * device.
*/
- public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
- CachedBluetoothDevice cachedDevice) {
+ public static Pair<Drawable, String> getBtClassDrawableWithDescription(
+ Context context, CachedBluetoothDevice cachedDevice) {
BluetoothClass btClass = cachedDevice.getBtClass();
if (btClass != null) {
switch (btClass.getMajorDeviceClass()) {
case BluetoothClass.Device.Major.COMPUTER:
- return new Pair<>(getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_bt_laptop),
+ return new Pair<>(
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_bt_laptop),
context.getString(R.string.bluetooth_talkback_computer));
case BluetoothClass.Device.Major.PHONE:
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_phone),
+ getBluetoothDrawable(context, com.android.internal.R.drawable.ic_phone),
context.getString(R.string.bluetooth_talkback_phone));
case BluetoothClass.Device.Major.PERIPHERAL:
@@ -115,8 +119,8 @@ public class BluetoothUtils {
case BluetoothClass.Device.Major.IMAGING:
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_settings_print),
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_settings_print),
context.getString(R.string.bluetooth_talkback_imaging));
default:
@@ -125,8 +129,9 @@ public class BluetoothUtils {
}
if (cachedDevice.isHearingAidDevice()) {
- return new Pair<>(getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_bt_hearing_aid),
+ return new Pair<>(
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_bt_hearing_aid),
context.getString(R.string.bluetooth_talkback_hearing_aids));
}
@@ -138,7 +143,8 @@ public class BluetoothUtils {
// The device should show hearing aid icon if it contains any hearing aid related
// profiles
if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) {
- return new Pair<>(getBluetoothDrawable(context, profileResId),
+ return new Pair<>(
+ getBluetoothDrawable(context, profileResId),
context.getString(R.string.bluetooth_talkback_hearing_aids));
}
if (resId == 0) {
@@ -153,42 +159,40 @@ public class BluetoothUtils {
if (btClass != null) {
if (doesClassMatch(btClass, BluetoothClass.PROFILE_HEADSET)) {
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_bt_headset_hfp),
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_bt_headset_hfp),
context.getString(R.string.bluetooth_talkback_headset));
}
if (doesClassMatch(btClass, BluetoothClass.PROFILE_A2DP)) {
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_bt_headphones_a2dp),
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_bt_headphones_a2dp),
context.getString(R.string.bluetooth_talkback_headphone));
}
}
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_settings_bluetooth).mutate(),
+ getBluetoothDrawable(context, com.android.internal.R.drawable.ic_settings_bluetooth)
+ .mutate(),
context.getString(R.string.bluetooth_talkback_bluetooth));
}
- /**
- * Get bluetooth drawable by {@code resId}
- */
+ /** Get bluetooth drawable by {@code resId} */
public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId) {
return context.getDrawable(resId);
}
- /**
- * Get colorful bluetooth icon with description
- */
- public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(Context context,
- CachedBluetoothDevice cachedDevice) {
+ /** Get colorful bluetooth icon with description */
+ public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(
+ Context context, CachedBluetoothDevice cachedDevice) {
final Resources resources = context.getResources();
- final Pair<Drawable, String> pair = BluetoothUtils.getBtDrawableWithDescription(context,
- cachedDevice);
+ final Pair<Drawable, String> pair =
+ BluetoothUtils.getBtDrawableWithDescription(context, cachedDevice);
if (pair.first instanceof BitmapDrawable) {
- return new Pair<>(new AdaptiveOutlineDrawable(
- resources, ((BitmapDrawable) pair.first).getBitmap()), pair.second);
+ return new Pair<>(
+ new AdaptiveOutlineDrawable(
+ resources, ((BitmapDrawable) pair.first).getBitmap()),
+ pair.second);
}
int hashCode;
@@ -198,15 +202,12 @@ public class BluetoothUtils {
hashCode = cachedDevice.getAddress().hashCode();
}
- return new Pair<>(buildBtRainbowDrawable(context,
- pair.first, hashCode), pair.second);
+ return new Pair<>(buildBtRainbowDrawable(context, pair.first, hashCode), pair.second);
}
- /**
- * Build Bluetooth device icon with rainbow
- */
- private static Drawable buildBtRainbowDrawable(Context context, Drawable drawable,
- int hashCode) {
+ /** Build Bluetooth device icon with rainbow */
+ private static Drawable buildBtRainbowDrawable(
+ Context context, Drawable drawable, int hashCode) {
final Resources resources = context.getResources();
// Deal with normal headset
@@ -222,38 +223,37 @@ public class BluetoothUtils {
return adaptiveIcon;
}
- /**
- * Get bluetooth icon with description
- */
- public static Pair<Drawable, String> getBtDrawableWithDescription(Context context,
- CachedBluetoothDevice cachedDevice) {
- final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(
- context, cachedDevice);
+ /** Get bluetooth icon with description */
+ public static Pair<Drawable, String> getBtDrawableWithDescription(
+ Context context, CachedBluetoothDevice cachedDevice) {
+ final Pair<Drawable, String> pair =
+ BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice);
final BluetoothDevice bluetoothDevice = cachedDevice.getDevice();
- final int iconSize = context.getResources().getDimensionPixelSize(
- R.dimen.bt_nearby_icon_size);
+ final int iconSize =
+ context.getResources().getDimensionPixelSize(R.dimen.bt_nearby_icon_size);
final Resources resources = context.getResources();
// Deal with advanced device icon
if (isAdvancedDetailsHeader(bluetoothDevice)) {
- final Uri iconUri = getUriMetaData(bluetoothDevice,
- BluetoothDevice.METADATA_MAIN_ICON);
+ final Uri iconUri = getUriMetaData(bluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON);
if (iconUri != null) {
try {
- context.getContentResolver().takePersistableUriPermission(iconUri,
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ context.getContentResolver()
+ .takePersistableUriPermission(
+ iconUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
} catch (SecurityException e) {
Log.e(TAG, "Failed to take persistable permission for: " + iconUri, e);
}
try {
- final Bitmap bitmap = MediaStore.Images.Media.getBitmap(
- context.getContentResolver(), iconUri);
+ final Bitmap bitmap =
+ MediaStore.Images.Media.getBitmap(
+ context.getContentResolver(), iconUri);
if (bitmap != null) {
- final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize,
- iconSize, false);
+ final Bitmap resizedBitmap =
+ Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false);
bitmap.recycle();
- return new Pair<>(new BitmapDrawable(resources,
- resizedBitmap), pair.second);
+ return new Pair<>(
+ new BitmapDrawable(resources, resizedBitmap), pair.second);
}
} catch (IOException e) {
Log.e(TAG, "Failed to get drawable for: " + iconUri, e);
@@ -280,8 +280,8 @@ public class BluetoothUtils {
return true;
}
// The metadata is for Android S
- String deviceType = getStringMetaData(bluetoothDevice,
- BluetoothDevice.METADATA_DEVICE_TYPE);
+ String deviceType =
+ getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)
|| TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_WATCH)
|| TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_DEFAULT)
@@ -306,8 +306,8 @@ public class BluetoothUtils {
return true;
}
// The metadata is for Android S
- String deviceType = getStringMetaData(bluetoothDevice,
- BluetoothDevice.METADATA_DEVICE_TYPE);
+ String deviceType =
+ getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) {
Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device ");
return true;
@@ -321,15 +321,15 @@ public class BluetoothUtils {
* @param device Must be one of the public constants in {@link BluetoothClass.Device}
* @return true if device class matches, false otherwise.
*/
- public static boolean isDeviceClassMatched(@NonNull BluetoothDevice bluetoothDevice,
- int device) {
+ public static boolean isDeviceClassMatched(
+ @NonNull BluetoothDevice bluetoothDevice, int device) {
final BluetoothClass bluetoothClass = bluetoothDevice.getBluetoothClass();
return bluetoothClass != null && bluetoothClass.getDeviceClass() == device;
}
private static boolean isAdvancedHeaderEnabled() {
- if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED,
- true)) {
+ if (!DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED, true)) {
Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false");
return false;
}
@@ -345,9 +345,7 @@ public class BluetoothUtils {
return false;
}
- /**
- * Create an Icon pointing to a drawable.
- */
+ /** Create an Icon pointing to a drawable. */
public static IconCompat createIconWithDrawable(Drawable drawable) {
Bitmap bitmap;
if (drawable instanceof BitmapDrawable) {
@@ -355,19 +353,15 @@ public class BluetoothUtils {
} else {
final int width = drawable.getIntrinsicWidth();
final int height = drawable.getIntrinsicHeight();
- bitmap = createBitmap(drawable,
- width > 0 ? width : 1,
- height > 0 ? height : 1);
+ bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1);
}
return IconCompat.createWithBitmap(bitmap);
}
- /**
- * Build device icon with advanced outline
- */
+ /** Build device icon with advanced outline */
public static Drawable buildAdvancedDrawable(Context context, Drawable drawable) {
- final int iconSize = context.getResources().getDimensionPixelSize(
- R.dimen.advanced_icon_size);
+ final int iconSize =
+ context.getResources().getDimensionPixelSize(R.dimen.advanced_icon_size);
final Resources resources = context.getResources();
Bitmap bitmap = null;
@@ -376,14 +370,12 @@ public class BluetoothUtils {
} else {
final int width = drawable.getIntrinsicWidth();
final int height = drawable.getIntrinsicHeight();
- bitmap = createBitmap(drawable,
- width > 0 ? width : 1,
- height > 0 ? height : 1);
+ bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1);
}
if (bitmap != null) {
- final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize,
- iconSize, false);
+ final Bitmap resizedBitmap =
+ Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false);
bitmap.recycle();
return new AdaptiveOutlineDrawable(resources, resizedBitmap, ICON_TYPE_ADVANCED);
}
@@ -391,9 +383,7 @@ public class BluetoothUtils {
return drawable;
}
- /**
- * Creates a drawable with specified width and height.
- */
+ /** Creates a drawable with specified width and height. */
public static Bitmap createBitmap(Drawable drawable, int width, int height) {
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
@@ -487,11 +477,8 @@ public class BluetoothUtils {
}
/**
- * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means:
- * 1) currently connected
- * 2) is Hearing Aid or LE Audio
- * OR
- * 3) connected profile matches currentAudioProfile
+ * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means: 1) currently
+ * connected 2) is Hearing Aid or LE Audio OR 3) connected profile matches currentAudioProfile
*
* @param cachedDevice the CachedBluetoothDevice
* @param audioManager audio manager to get the current audio profile
@@ -519,8 +506,11 @@ public class BluetoothUtils {
// It would show in Available Devices group.
if (cachedDevice.isConnectedAshaHearingAidDevice()
|| cachedDevice.isConnectedLeAudioDevice()) {
- Log.d(TAG, "isFilterMatched() device : "
- + cachedDevice.getName() + ", the profile is connected.");
+ Log.d(
+ TAG,
+ "isFilterMatched() device : "
+ + cachedDevice.getName()
+ + ", the profile is connected.");
return true;
}
// According to the current audio profile type,
@@ -541,11 +531,79 @@ public class BluetoothUtils {
return isFilterMatched;
}
+ /** Returns if the le audio sharing is enabled. */
+ public static boolean isAudioSharingEnabled() {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ return Flags.enableLeAudioSharing()
+ && adapter.isLeAudioBroadcastSourceSupported()
+ == BluetoothStatusCodes.FEATURE_SUPPORTED
+ && adapter.isLeAudioBroadcastAssistantSupported()
+ == BluetoothStatusCodes.FEATURE_SUPPORTED;
+ }
+
+ /** Returns if the broadcast is on-going. */
+ @WorkerThread
+ public static boolean isBroadcasting(@Nullable LocalBluetoothManager manager) {
+ if (manager == null) return false;
+ LocalBluetoothLeBroadcast broadcast =
+ manager.getProfileManager().getLeAudioBroadcastProfile();
+ return broadcast != null && broadcast.isEnabled(null);
+ }
+
+ /**
+ * Check if {@link CachedBluetoothDevice} has connected to a broadcast source.
+ *
+ * @param cachedDevice The cached bluetooth device to check.
+ * @param localBtManager The BT manager to provide BT functions.
+ * @return Whether the device has connected to a broadcast source.
+ */
+ @WorkerThread
+ public static boolean hasConnectedBroadcastSource(
+ CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) {
+ if (localBtManager == null) {
+ Log.d(TAG, "Skip check hasConnectedBroadcastSource due to bt manager is null");
+ return false;
+ }
+ LocalBluetoothLeBroadcastAssistant assistant =
+ localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (assistant == null) {
+ Log.d(TAG, "Skip check hasConnectedBroadcastSource due to assistant profile is null");
+ return false;
+ }
+ List<BluetoothLeBroadcastReceiveState> sourceList =
+ assistant.getAllSources(cachedDevice.getDevice());
+ if (!sourceList.isEmpty() && sourceList.stream().anyMatch(BluetoothUtils::isConnected)) {
+ Log.d(
+ TAG,
+ "Lead device has connected broadcast source, device = "
+ + cachedDevice.getDevice().getAnonymizedAddress());
+ return true;
+ }
+ // Return true if member device is in broadcast.
+ for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
+ List<BluetoothLeBroadcastReceiveState> list =
+ assistant.getAllSources(device.getDevice());
+ if (!list.isEmpty() && list.stream().anyMatch(BluetoothUtils::isConnected)) {
+ Log.d(
+ TAG,
+ "Member device has connected broadcast source, device = "
+ + device.getDevice().getAnonymizedAddress());
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Checks the connectivity status based on the provided broadcast receive state. */
+ @WorkerThread
+ public static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
+ return state.getBisSyncState().stream().anyMatch(bitmap -> bitmap != 0);
+ }
+
/**
- * Checks if the Bluetooth device is an available hearing device, which means:
- * 1) currently connected
- * 2) is Hearing Aid
- * 3) connected profile match hearing aid related profiles (e.g. ASHA, HAP)
+ * Checks if the Bluetooth device is an available hearing device, which means: 1) currently
+ * connected 2) is Hearing Aid 3) connected profile match hearing aid related profiles (e.g.
+ * ASHA, HAP)
*
* @param cachedDevice the CachedBluetoothDevice
* @return if the device is Available hearing device
@@ -553,19 +611,20 @@ public class BluetoothUtils {
@WorkerThread
public static boolean isAvailableHearingDevice(CachedBluetoothDevice cachedDevice) {
if (isDeviceConnected(cachedDevice) && cachedDevice.isConnectedHearingAidDevice()) {
- Log.d(TAG, "isFilterMatched() device : "
- + cachedDevice.getName() + ", the profile is connected.");
+ Log.d(
+ TAG,
+ "isFilterMatched() device : "
+ + cachedDevice.getName()
+ + ", the profile is connected.");
return true;
}
return false;
}
/**
- * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means:
- * 1) currently connected
- * 2) is not Hearing Aid or LE Audio
- * AND
- * 3) connected profile does not match currentAudioProfile
+ * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means: 1) currently
+ * connected 2) is not Hearing Aid or LE Audio AND 3) connected profile does not match
+ * currentAudioProfile
*
* @param cachedDevice the CachedBluetoothDevice
* @param audioManager audio manager to get the current audio profile
@@ -675,29 +734,28 @@ public class BluetoothUtils {
}
/**
- * Returns the BluetoothDevice's exclusive manager
- * ({@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the
- * given set, otherwise null.
+ * Returns the BluetoothDevice's exclusive manager ({@link
+ * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the given
+ * set, otherwise null.
*/
@Nullable
private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) {
- byte[] exclusiveManagerNameBytes = bluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
+ byte[] exclusiveManagerNameBytes =
+ bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
if (exclusiveManagerNameBytes == null) {
- Log.d(TAG, "Bluetooth device " + bluetoothDevice.getName()
- + " doesn't have exclusive manager");
+ Log.d(
+ TAG,
+ "Bluetooth device "
+ + bluetoothDevice.getName()
+ + " doesn't have exclusive manager");
return null;
}
String exclusiveManagerName = new String(exclusiveManagerNameBytes);
- return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName
- : null;
+ return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null;
}
- /**
- * Checks if given package is installed
- */
- private static boolean isPackageInstalled(Context context,
- String packageName) {
+ /** Checks if given package is installed */
+ private static boolean isPackageInstalled(Context context, String packageName) {
PackageManager packageManager = context.getPackageManager();
try {
packageManager.getPackageInfo(packageName, 0);
@@ -709,13 +767,12 @@ public class BluetoothUtils {
}
/**
- * A BluetoothDevice is exclusively managed if
- * 1) it has field {@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata.
- * 2) the exclusive manager app name is in the allowlist.
- * 3) the exclusive manager app is installed.
+ * A BluetoothDevice is exclusively managed if 1) it has field {@link
+ * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app name is
+ * in the allowlist. 3) the exclusive manager app is installed.
*/
- public static boolean isExclusivelyManagedBluetoothDevice(@NonNull Context context,
- @NonNull BluetoothDevice bluetoothDevice) {
+ public static boolean isExclusivelyManagedBluetoothDevice(
+ @NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) {
String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice);
if (exclusiveManagerName == null) {
return false;
@@ -728,9 +785,7 @@ public class BluetoothUtils {
}
}
- /**
- * Return the allowlist for exclusive manager names.
- */
+ /** Return the allowlist for exclusive manager names. */
@NonNull
public static Set<String> getExclusiveManagers() {
return EXCLUSIVE_MANAGERS;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 4777b0de0732..04516eba250e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -16,6 +16,8 @@
package com.android.settingslib.bluetooth;
+import static com.android.settingslib.flags.Flags.enableSetPreferredTransportForLeAudioDevice;
+
import android.annotation.CallbackExecutor;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
@@ -288,6 +290,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
mLocalNapRoleConnected = true;
}
}
+ if (enableSetPreferredTransportForLeAudioDevice()
+ && profile instanceof HidProfile) {
+ updatePreferredTransport();
+ }
} else if (profile instanceof MapProfile
&& newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
profile.setEnabled(mDevice, false);
@@ -300,12 +306,34 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
mLocalNapRoleConnected = false;
}
+ if (enableSetPreferredTransportForLeAudioDevice()
+ && profile instanceof LeAudioProfile) {
+ updatePreferredTransport();
+ }
+
HearingAidStatsLogUtils.updateHistoryIfNeeded(mContext, this, profile, newProfileState);
}
fetchActiveDevices();
}
+ private void updatePreferredTransport() {
+ if (mProfiles.stream().noneMatch(p -> p instanceof LeAudioProfile)
+ || mProfiles.stream().noneMatch(p -> p instanceof HidProfile)) {
+ return;
+ }
+ // Both LeAudioProfile and HidProfile are connectable.
+ if (!mProfileManager
+ .getHidProfile()
+ .setPreferredTransport(
+ mDevice,
+ mProfileManager.getLeAudioProfile().isEnabled(mDevice)
+ ? BluetoothDevice.TRANSPORT_LE
+ : BluetoothDevice.TRANSPORT_BREDR)) {
+ Log.w(TAG, "Fail to set preferred transport");
+ }
+ }
+
@VisibleForTesting
void setProfileConnectedStatus(int profileId, boolean isFailed) {
switch (profileId) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index 5b91ac9d3dab..b849d44622b2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -27,6 +27,8 @@ import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.android.settingslib.R;
import java.util.List;
@@ -187,6 +189,14 @@ public class HidProfile implements LocalBluetoothProfile {
}
}
+ /** Set preferred transport for the device */
+ public boolean setPreferredTransport(@NonNull BluetoothDevice device, int transport) {
+ if (mService != null) {
+ mService.setPreferredTransport(device, transport);
+ }
+ return false;
+ }
+
protected void finalize() {
Log.d(TAG, "finalize()");
if (mService != null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 64cf5c640143..9df23aa2fe29 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -42,6 +42,7 @@ import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
+import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -78,6 +79,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
public static final String ACTION_LE_AUDIO_SHARING_STATE_CHANGE =
"com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE";
public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE";
+ public static final String EXTRA_BLUETOOTH_DEVICE = "BLUETOOTH_DEVICE";
public static final int BROADCAST_STATE_UNKNOWN = 0;
public static final int BROADCAST_STATE_ON = 1;
public static final int BROADCAST_STATE_OFF = 2;
@@ -335,7 +337,6 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
+ ", sourceId = "
+ sourceId);
}
- updateFallbackActiveDeviceIfNeeded();
}
@Override
@@ -467,6 +468,15 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
mServiceBroadcast.startBroadcast(settings);
}
+ /** Checks if the broadcast is playing. */
+ public boolean isPlaying(int broadcastId) {
+ if (mServiceBroadcast == null) {
+ Log.d(TAG, "check isPlaying failed, the BluetoothLeBroadcast is null.");
+ return false;
+ }
+ return mServiceBroadcast.isPlaying(broadcastId);
+ }
+
private BluetoothLeBroadcastSettings buildBroadcastSettings(
boolean isPublic,
@Nullable String broadcastName,
@@ -1024,6 +1034,16 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
/** Update fallback active device if needed. */
public void updateFallbackActiveDeviceIfNeeded() {
+ if (mServiceBroadcast == null) {
+ Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to broadcast profile is null");
+ return;
+ }
+ List<BluetoothLeBroadcastMetadata> sources = mServiceBroadcast.getAllBroadcastMetadata();
+ if (sources.stream()
+ .noneMatch(source -> mServiceBroadcast.isPlaying(source.getBroadcastId()))) {
+ Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no broadcast ongoing");
+ return;
+ }
if (mServiceBroadcastAssistant == null) {
Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null");
return;
@@ -1116,10 +1136,19 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
return;
}
+ if (isWorkProfile(mContext)) {
+ Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered for work profile.");
+ return;
+ }
Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE);
intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, state);
intent.setPackage(mContext.getPackageName());
Log.e(TAG, "notifyBroadcastStateChange for state = " + state);
mContext.sendBroadcast(intent);
}
+
+ private boolean isWorkProfile(Context context) {
+ UserManager userManager = context.getSystemService(UserManager.class);
+ return userManager != null && userManager.isManagedProfile();
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 79e4c374667e..4055986e8a57 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -572,8 +572,7 @@ public class LocalBluetoothProfileManager {
return mSapProfile;
}
- @VisibleForTesting
- HidProfile getHidProfile() {
+ public HidProfile getHidProfile() {
return mHidProfile;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index b015b2bce60a..46f229035839 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -184,7 +184,7 @@ public class EditUserInfoController {
dialogHelper
.setTitle(R.string.user_info_settings_title)
.addCustomView(content)
- .setPositiveButton(android.R.string.ok, view -> {
+ .setPositiveButton(R.string.okay, view -> {
Drawable newUserIcon = mEditUserPhotoController != null
? mEditUserPhotoController.getNewUserPhotoDrawable()
: null;
@@ -201,7 +201,7 @@ public class EditUserInfoController {
}
dialogHelper.getDialog().dismiss();
})
- .setBackButton(android.R.string.cancel, view -> {
+ .setBackButton(R.string.cancel, view -> {
clear();
if (cancelCallback != null) {
cancelCallback.run();
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index b9748883b25d..fef05612a8cb 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -240,6 +240,56 @@ public class ApplicationsStateTest {
}
@Test
+ public void testDownloadAndLauncherNotInQuietAcceptsCorrectApps() {
+ mEntry.isHomeApp = false;
+ mEntry.hasLauncherEntry = false;
+
+ // should include updated system apps
+ when(mEntry.info.isInstantApp()).thenReturn(false);
+ mEntry.info.flags = ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+ assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET.filterApp(mEntry))
+ .isTrue();
+
+ // should not include system apps other than the home app
+ mEntry.info.flags = ApplicationInfo.FLAG_SYSTEM;
+ mEntry.isHomeApp = false;
+ mEntry.hasLauncherEntry = false;
+ assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET.filterApp(mEntry))
+ .isFalse();
+
+ // should include the home app
+ mEntry.isHomeApp = true;
+ assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET.filterApp(mEntry))
+ .isTrue();
+
+ // should include any System app with a launcher entry
+ mEntry.isHomeApp = false;
+ mEntry.hasLauncherEntry = true;
+ assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET.filterApp(mEntry))
+ .isTrue();
+
+ // should not include updated system apps when in quiet mode
+ when(mEntry.info.isInstantApp()).thenReturn(false);
+ mEntry.info.flags = ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+ mEntry.hideInQuietMode = true;
+ assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET.filterApp(mEntry))
+ .isFalse();
+
+ // should not include the home app when in quiet mode
+ mEntry.isHomeApp = true;
+ mEntry.hideInQuietMode = true;
+ assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET.filterApp(mEntry))
+ .isFalse();
+
+ // should not include any System app with a launcher entry when in quiet mode
+ mEntry.isHomeApp = false;
+ mEntry.hasLauncherEntry = true;
+ mEntry.hideInQuietMode = true;
+ assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET.filterApp(mEntry))
+ .isFalse();
+ }
+
+ @Test
public void testOtherAppsRejectsLegacyGame() {
mEntry.info.flags = ApplicationInfo.FLAG_IS_GAME;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 1246fd85ee16..f197f9ee0baf 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -17,6 +17,7 @@ package com.android.settingslib.bluetooth;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
@@ -25,6 +26,7 @@ import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -44,6 +46,9 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import java.util.ArrayList;
+import java.util.List;
+
@RunWith(RobolectricTestRunner.class)
public class BluetoothUtilsTest {
@@ -55,6 +60,16 @@ public class BluetoothUtilsTest {
private AudioManager mAudioManager;
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private LocalBluetoothLeBroadcast mBroadcast;
+ @Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private LocalBluetoothManager mLocalBluetoothManager;
+ @Mock
+ private LocalBluetoothLeBroadcastAssistant mAssistant;
+ @Mock
+ private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState;
private Context mContext;
private static final String STRING_METADATA = "string_metadata";
@@ -72,6 +87,9 @@ public class BluetoothUtilsTest {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
+ when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
+ when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
}
@Test
@@ -432,6 +450,30 @@ public class BluetoothUtilsTest {
}
@Test
+ public void testIsBroadcasting_broadcastEnabled_returnTrue() {
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
+ assertThat(BluetoothUtils.isBroadcasting(mLocalBluetoothManager)).isEqualTo(true);
+ }
+
+ @Test
+ public void testHasConnectedBroadcastSource_deviceConnectedToBroadcastSource() {
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+
+ List<Long> bisSyncState = new ArrayList<>();
+ bisSyncState.add(1L);
+ when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(any())).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isEqualTo(true);
+ }
+
+ @Test
public void isAvailableHearingDevice_isConnectedHearingAid_returnTure() {
when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 5996dbb322fc..646e9ebd4f09 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -15,6 +15,8 @@
*/
package com.android.settingslib.bluetooth;
+import static com.android.settingslib.flags.Flags.FLAG_ENABLE_SET_PREFERRED_TRANSPORT_FOR_LE_AUDIO_DEVICE;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -86,6 +88,9 @@ public class CachedBluetoothDeviceTest {
private HapClientProfile mHapClientProfile;
@Mock
private LeAudioProfile mLeAudioProfile;
+
+ @Mock
+ private HidProfile mHidProfile;
@Mock
private BluetoothDevice mDevice;
@Mock
@@ -104,6 +109,7 @@ public class CachedBluetoothDeviceTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TV_MEDIA_OUTPUT_DIALOG);
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_SET_PREFERRED_TRANSPORT_FOR_LE_AUDIO_DEVICE);
mContext = RuntimeEnvironment.application;
mAudioManager = mContext.getSystemService(AudioManager.class);
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
@@ -118,6 +124,8 @@ public class CachedBluetoothDeviceTest {
when(mHearingAidProfile.getProfileId()).thenReturn(BluetoothProfile.HEARING_AID);
when(mLeAudioProfile.isProfileReady()).thenReturn(true);
when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO);
+ when(mHidProfile.isProfileReady()).thenReturn(true);
+ when(mHidProfile.getProfileId()).thenReturn(BluetoothProfile.HID_HOST);
mCachedDevice = spy(new CachedBluetoothDevice(mContext, mProfileManager, mDevice));
mSubCachedDevice = spy(new CachedBluetoothDevice(mContext, mProfileManager, mSubDevice));
doAnswer((invocation) -> mBatteryLevel).when(mCachedDevice).getBatteryLevel();
@@ -1819,6 +1827,32 @@ public class CachedBluetoothDeviceTest {
assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse();
}
+ @Test
+ public void leAudioHidDevice_leAudioEnabled_setPreferredTransportToLE() {
+
+ when(mProfileManager.getHidProfile()).thenReturn(mHidProfile);
+ when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+ when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true);
+
+ updateProfileStatus(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+ updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED);
+
+ verify(mHidProfile).setPreferredTransport(mDevice, BluetoothDevice.TRANSPORT_LE);
+ }
+
+ @Test
+ public void leAudioHidDevice_leAudioDisabled_setPreferredTransportToBredr() {
+ when(mProfileManager.getHidProfile()).thenReturn(mHidProfile);
+ when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+ when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(false);
+
+ updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED);
+ updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_DISCONNECTED);
+ updateProfileStatus(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+
+ verify(mHidProfile).setPreferredTransport(mDevice, BluetoothDevice.TRANSPORT_BREDR);
+ }
+
private HearingAidInfo getLeftAshaHearingAidInfo() {
return new HearingAidInfo.Builder()
.setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT)
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 17d9f1b87fac..097840ead0d3 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -338,4 +338,7 @@
<!-- Value to use as default scale for fonts -->
<item name="def_device_font_scale" format="float" type="dimen">1.0</item>
+
+ <!-- The default ringer mode. See `AudioManager` for list of valid values. -->
+ <integer name="def_ringer_mode">2</integer>
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1ead14ab6f4c..096cccc1f94a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3943,8 +3943,10 @@ public class SettingsProvider extends ContentProvider {
globalSettings.updateSettingLocked(Settings.Global.ZEN_MODE,
Integer.toString(Settings.Global.ZEN_MODE_OFF), null,
true, SettingsState.SYSTEM_PACKAGE_NAME);
+ final int defaultRingerMode =
+ getContext().getResources().getInteger(R.integer.def_ringer_mode);
globalSettings.updateSettingLocked(Settings.Global.MODE_RINGER,
- Integer.toString(AudioManager.RINGER_MODE_NORMAL), null,
+ Integer.toString(defaultRingerMode), null,
true, SettingsState.SYSTEM_PACKAGE_NAME);
}
currentVersion = 119;
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index f42efe224e4b..c891dfc89c28 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -840,6 +840,8 @@ public class SettingsBackupTest {
Settings.Secure.BIOMETRIC_APP_ENABLED,
Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED,
Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED,
+ Settings.Secure.BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED,
+ Settings.Secure.BIOMETRIC_FACE_VIRTUAL_ENABLED,
Settings.Secure.BLUETOOTH_ADDR_VALID,
Settings.Secure.BLUETOOTH_ADDRESS,
Settings.Secure.BLUETOOTH_NAME,
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 42952de1b2b9..5ac0e449b8e1 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -50,7 +50,6 @@ import android.os.AsyncTask;
import android.os.Binder;
import android.os.BugreportManager;
import android.os.BugreportManager.BugreportCallback;
-import android.os.BugreportManager.BugreportCallback.BugreportErrorCode;
import android.os.BugreportParams;
import android.os.Bundle;
import android.os.FileUtils;
@@ -169,6 +168,8 @@ public class BugreportProgressService extends Service {
static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
static final String EXTRA_ORIGINAL_INTENT = "android.intent.extra.ORIGINAL_INTENT";
static final String EXTRA_INFO = "android.intent.extra.INFO";
+ static final String EXTRA_EXTRA_ATTACHMENT_URI =
+ "android.intent.extra.EXTRA_ATTACHMENT_URI";
private static final int MSG_SERVICE_COMMAND = 1;
private static final int MSG_DELAYED_SCREENSHOT = 2;
@@ -634,9 +635,10 @@ public class BugreportProgressService extends Service {
long nonce = intent.getLongExtra(EXTRA_BUGREPORT_NONCE, 0);
String baseName = getBugreportBaseName(bugreportType);
String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
+ Uri extraAttachment = intent.getParcelableExtra(EXTRA_EXTRA_ATTACHMENT_URI, Uri.class);
- BugreportInfo info = new BugreportInfo(mContext, baseName, name,
- shareTitle, shareDescription, bugreportType, mBugreportsDir, nonce);
+ BugreportInfo info = new BugreportInfo(mContext, baseName, name, shareTitle,
+ shareDescription, bugreportType, mBugreportsDir, nonce, extraAttachment);
synchronized (mLock) {
if (info.bugreportFile.exists()) {
Log.e(TAG, "Failed to start bugreport generation, the requested bugreport file "
@@ -1184,6 +1186,10 @@ public class BugreportProgressService extends Service {
clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
attachments.add(screenshotUri);
}
+ if (info.extraAttachment != null) {
+ clipData.addItem(new ClipData.Item(null, null, null, info.extraAttachment));
+ attachments.add(info.extraAttachment);
+ }
intent.setClipData(clipData);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
@@ -2042,6 +2048,9 @@ public class BugreportProgressService extends Service {
*/
final long nonce;
+ @Nullable
+ public Uri extraAttachment = null;
+
private final Object mLock = new Object();
/**
@@ -2049,7 +2058,8 @@ public class BugreportProgressService extends Service {
*/
BugreportInfo(Context context, String baseName, String name,
@Nullable String shareTitle, @Nullable String shareDescription,
- @BugreportParams.BugreportMode int type, File bugreportsDir, long nonce) {
+ @BugreportParams.BugreportMode int type, File bugreportsDir, long nonce,
+ @Nullable Uri extraAttachment) {
this.context = context;
this.name = this.initialName = name;
this.shareTitle = shareTitle == null ? "" : shareTitle;
@@ -2058,6 +2068,7 @@ public class BugreportProgressService extends Service {
this.nonce = nonce;
this.baseName = baseName;
this.bugreportFile = new File(bugreportsDir, getFileName(this, ".zip"));
+ this.extraAttachment = extraAttachment;
}
void createBugreportFile() {
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index 648cc3b035c5..a98625f0137c 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -40,7 +40,7 @@
android:exported="true"
android:label="@string/accessibility_menu_settings_name"
android:launchMode="singleTop"
- android:theme="@style/Theme.SettingsBase">
+ android:theme="@style/SettingsTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -59,4 +59,4 @@
<action android:name="android.intent.action.VOICE_COMMAND" />
</intent>
</queries>
-</manifest> \ No newline at end of file
+</manifest>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
index 41691552f714..a138fa9be613 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
@@ -21,6 +21,11 @@
<item name="android:colorControlNormal">@color/colorControlNormal</item>
</style>
+ <style name="SettingsTheme" parent="Theme.SettingsBase">
+ <!-- Quick fix so that the preference page doesn't render under its parent header. -->
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+ </style>
+
<!--The basic theme for service and test case only-->
<style name="A11yMenuBaseTheme" parent="android:Theme.DeviceDefault.Light">
<item name="android:windowActionBar">false</item>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 5f3d1eafb4fb..0ab99fac9ba3 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -30,8 +30,6 @@ import static com.android.systemui.accessibility.accessibilitymenu.Accessibility
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME;
-import static com.google.common.truth.Truth.assertThat;
-
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
import android.app.KeyguardManager;
@@ -449,7 +447,10 @@ public class AccessibilityMenuServiceTest {
closeScreen();
wakeUpScreen();
- assertThat(isMenuVisible()).isFalse();
+ TestUtils.waitUntil("Menu did not close.",
+ TIMEOUT_UI_CHANGE_S,
+ () -> !isMenuVisible()
+ );
}
@Test
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index 2a32b5854425..1858c80ca901 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -45,4 +45,5 @@ aconfig_declarations {
java_aconfig_library {
name: "com_android_systemui_flags_lib",
aconfig_declarations: "com_android_systemui_flags",
+ sdk_version: "system_current",
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index f6616dbfe11a..fd2fa07f5e9d 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -148,6 +148,16 @@ flag {
}
flag {
+ name: "pss_app_selector_recents_split_screen"
+ namespace: "systemui"
+ description: "Allows recent apps selected for partial screenshare to be launched in split screen mode"
+ bug: "320449039"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "notifications_background_icons"
namespace: "systemui"
description: "Moves part of the notification icon updates to the background."
@@ -328,6 +338,16 @@ flag {
}
flag {
+ name: "status_bar_monochrome_icons_fix"
+ namespace: "systemui"
+ description: "Fixes the status bar icon size when drawing InsetDrawables (ie. monochrome icons)"
+ bug: "329091967"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "compose_bouncer"
namespace: "systemui"
description: "Use the new compose bouncer in SystemUI"
@@ -724,3 +744,10 @@ flag {
" Compose for the UI."
bug: "325099249"
}
+
+flag {
+ name: "keyboard_docking_indicator"
+ namespace: "systemui"
+ description: "Glow bar indicator reveals upon keyboard docking."
+ bug: "324600132"
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
index 4d327e1d8beb..6c982a045084 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
@@ -41,9 +41,9 @@ fun BackAnimationSpec.Companion.createFloatingSurfaceAnimationSpec(
maxMarginXdp: Float,
maxMarginYdp: Float,
minScale: Float,
- translateXEasing: Interpolator = Interpolators.STANDARD_DECELERATE,
+ translateXEasing: Interpolator = Interpolators.BACK_GESTURE,
translateYEasing: Interpolator = Interpolators.LINEAR,
- scaleEasing: Interpolator = Interpolators.STANDARD_DECELERATE,
+ scaleEasing: Interpolator = Interpolators.BACK_GESTURE,
): BackAnimationSpec {
return BackAnimationSpec { backEvent, progressY, result ->
val displayMetrics = displayMetricsProvider()
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt
new file mode 100644
index 000000000000..d50979ccd01d
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.surfaceeffects
+
+import android.graphics.Paint
+import android.graphics.RenderEffect
+
+/**
+ * A callback with a [Paint] object that contains shader info, which is triggered every frame while
+ * animation is playing. Note that the [Paint] object here is always the same instance.
+ *
+ * This approach is more performant than other ones because [RenderEffect] forces an intermediate
+ * render pass of the View to a texture to feed into it.
+ *
+ * The usage of this callback is as follows:
+ * <pre>{@code
+ * private var paint: Paint? = null
+ * // Override [View.onDraw].
+ * override fun onDraw(canvas: Canvas) {
+ * // RuntimeShader requires hardwareAcceleration.
+ * if (!canvas.isHardwareAccelerated) return
+ *
+ * paint?.let { canvas.drawPaint(it) }
+ * }
+ *
+ * // Given that this is called [PaintDrawCallback.onDraw]
+ * fun draw(paint: Paint) {
+ * this.paint = paint
+ *
+ * // Must call invalidate to trigger View#onDraw
+ * invalidate()
+ * }
+ * }</pre>
+ *
+ * Please refer to [RenderEffectDrawCallback] for alternative approach.
+ */
+interface PaintDrawCallback {
+ fun onDraw(paint: Paint)
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt
new file mode 100644
index 000000000000..db7ee58090a9
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.surfaceeffects
+
+import android.graphics.RenderEffect
+
+/**
+ * A callback with a [RenderEffect] object that contains shader info, which is triggered every frame
+ * while animation is playing. Note that the [RenderEffect] instance is different each time to
+ * update shader uniforms.
+ *
+ * The usage of this callback is as follows:
+ * <pre>{@code
+ * private val xEffectDrawingCallback = RenderEffectDrawCallback() {
+ * val myOtherRenderEffect = createOtherRenderEffect()
+ * val chainEffect = RenderEffect.createChainEffect(renderEffect, myOtherRenderEffect)
+ * myView.setRenderEffect(chainEffect)
+ * }
+ *
+ * private val xEffect = XEffect(config, xEffectDrawingCallback)
+ * }</pre>
+ */
+interface RenderEffectDrawCallback {
+ fun onDraw(renderEffect: RenderEffect)
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt
index 1c763e8c6108..211b84f25369 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt
@@ -22,6 +22,8 @@ import android.animation.ValueAnimator
import android.graphics.Paint
import android.graphics.RenderEffect
import android.view.View
+import com.android.systemui.surfaceeffects.PaintDrawCallback
+import com.android.systemui.surfaceeffects.RenderEffectDrawCallback
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader
@@ -334,52 +336,31 @@ private constructor(
)
}
- companion object {
+ /**
+ * States of the loading effect animation.
+ *
+ * <p>The state is designed to be follow the order below: [AnimationState.EASE_IN],
+ * [AnimationState.MAIN], [AnimationState.EASE_OUT]. Note that ease in and out don't necessarily
+ * mean the acceleration and deceleration in the animation curve. They simply mean each stage of
+ * the animation. (i.e. Intro, core, and rest)
+ */
+ enum class AnimationState {
+ EASE_IN,
+ MAIN,
+ EASE_OUT,
+ NOT_PLAYING
+ }
+
+ /** Optional callback that is triggered when the animation state changes. */
+ interface AnimationStateChangedCallback {
/**
- * States of the loading effect animation.
- *
- * <p>The state is designed to be follow the order below: [AnimationState.EASE_IN],
- * [AnimationState.MAIN], [AnimationState.EASE_OUT]. Note that ease in and out don't
- * necessarily mean the acceleration and deceleration in the animation curve. They simply
- * mean each stage of the animation. (i.e. Intro, core, and rest)
+ * A callback that's triggered when the [AnimationState] changes. Example usage is
+ * performing a cleanup when [AnimationState] becomes [NOT_PLAYING].
*/
- enum class AnimationState {
- EASE_IN,
- MAIN,
- EASE_OUT,
- NOT_PLAYING
- }
-
- /** Client must implement one of the draw callbacks. */
- interface PaintDrawCallback {
- /**
- * A callback with a [Paint] object that contains shader info, which is triggered every
- * frame while animation is playing. Note that the [Paint] object here is always the
- * same instance.
- */
- fun onDraw(loadingPaint: Paint)
- }
-
- interface RenderEffectDrawCallback {
- /**
- * A callback with a [RenderEffect] object that contains shader info, which is triggered
- * every frame while animation is playing. Note that the [RenderEffect] instance is
- * different each time to update shader uniforms.
- */
- fun onDraw(loadingRenderEffect: RenderEffect)
- }
-
- /** Optional callback that is triggered when the animation state changes. */
- interface AnimationStateChangedCallback {
- /**
- * A callback that's triggered when the [AnimationState] changes. Example usage is
- * performing a cleanup when [AnimationState] becomes [NOT_PLAYING].
- */
- fun onStateChanged(oldState: AnimationState, newState: AnimationState) {}
- }
+ fun onStateChanged(oldState: AnimationState, newState: AnimationState) {}
+ }
+ private companion object {
private const val MS_TO_SEC = 0.001f
-
- private val TAG = LoadingEffect::class.java.simpleName
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index 0f3d3dc2847f..d55d4e494980 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -160,7 +160,9 @@ private fun StandardLayout(
FoldAware(
modifier =
modifier.padding(
+ start = 32.dp,
top = 92.dp,
+ end = 32.dp,
bottom = 48.dp,
),
viewModel = viewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 3ec5508c81b3..d59f1f5bbe25 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -22,11 +22,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.bouncer.ui.BouncerDialogFactory
@@ -35,9 +32,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
object Bouncer {
object Elements {
@@ -57,13 +52,7 @@ constructor(
override val key = Scenes.Bouncer
override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- MutableStateFlow(
- mapOf(
- Back to UserActionResult(Scenes.Lockscreen),
- Swipe(SwipeDirection.Down) to UserActionResult(Scenes.Lockscreen),
- )
- )
- .asStateFlow()
+ viewModel.destinationScenes
@Composable
override fun SceneScope.Content(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index a78c2c0d16c6..07c2d3c95e01 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -432,12 +432,12 @@ private fun offset(
}
}
-private const val DOT_DIAMETER_DP = 16
-private const val SELECTED_DOT_DIAMETER_DP = 24
+private const val DOT_DIAMETER_DP = 14
+private const val SELECTED_DOT_DIAMETER_DP = (DOT_DIAMETER_DP * 1.5).toInt()
private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83
private const val SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS = 750
-private const val LINE_STROKE_WIDTH_DP = 16
-private const val FAILURE_ANIMATION_DOT_DIAMETER_DP = 13
+private const val LINE_STROKE_WIDTH_DP = DOT_DIAMETER_DP
+private const val FAILURE_ANIMATION_DOT_DIAMETER_DP = (DOT_DIAMETER_DP * 0.81f).toInt()
private const val FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS = 50
private const val FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS = 33
private const val FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION = 617
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index bdd888f45182..4533f58c1c37 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -26,6 +26,7 @@ import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.res.R
@@ -41,6 +42,11 @@ object Communal {
}
val sceneTransitions = transitions {
+ to(CommunalScenes.Communal, key = CommunalTransitionKeys.SimpleFade) {
+ spec = tween(durationMillis = 250)
+ fade(Communal.Elements.Scrim)
+ fade(Communal.Elements.Content)
+ }
to(CommunalScenes.Communal) {
spec = tween(durationMillis = 1000)
translate(Communal.Elements.Content, Edge.Right)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
index 55f7f69a08d6..52cbffbc0177 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
@@ -19,8 +19,6 @@ package com.android.systemui.keyguard.ui.composable
import com.android.systemui.keyguard.ui.composable.blueprint.CommunalBlueprintModule
import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule
import com.android.systemui.keyguard.ui.composable.blueprint.ShortcutsBesideUdfpsBlueprintModule
-import com.android.systemui.keyguard.ui.composable.blueprint.SplitShadeWeatherClockBlueprintModule
-import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockBlueprintModule
import com.android.systemui.keyguard.ui.composable.section.OptionalSectionModule
import dagger.Module
@@ -31,8 +29,6 @@ import dagger.Module
DefaultBlueprintModule::class,
OptionalSectionModule::class,
ShortcutsBesideUdfpsBlueprintModule::class,
- SplitShadeWeatherClockBlueprintModule::class,
- WeatherClockBlueprintModule::class,
],
)
interface LockscreenSceneBlueprintModule
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
index acd9e3dc83cb..c6fe81af59b7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
@@ -25,6 +25,9 @@ import com.android.compose.animation.scene.transitions
import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey
import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey
import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smartspaceElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition.transitioningToLargeClock
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition.transitioningToSmallClock
+import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockElementKeys.largeWeatherClockElementKeyList
import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceInTransition.Companion.CLOCK_IN_MILLIS
import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceInTransition.Companion.CLOCK_IN_START_DELAY_MILLIS
import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceOutTransition.Companion.CLOCK_OUT_MILLIS
@@ -34,30 +37,45 @@ import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSi
object ClockTransition {
val defaultClockTransitions = transitions {
from(ClockScenes.smallClockScene, to = ClockScenes.largeClockScene) {
- transitioningToLargeClock()
+ transitioningToLargeClock(largeClockElements = listOf(largeClockElementKey))
}
from(ClockScenes.largeClockScene, to = ClockScenes.smallClockScene) {
- transitioningToSmallClock()
+ transitioningToSmallClock(largeClockElements = listOf(largeClockElementKey))
}
from(ClockScenes.splitShadeLargeClockScene, to = ClockScenes.largeClockScene) {
- spec = tween(1000, easing = LinearEasing)
+ spec = tween(300, easing = LinearEasing)
+ }
+
+ from(WeatherClockScenes.largeClockScene, to = ClockScenes.smallClockScene) {
+ transitioningToSmallClock(largeClockElements = largeWeatherClockElementKeyList)
+ }
+
+ from(ClockScenes.smallClockScene, to = WeatherClockScenes.largeClockScene) {
+ transitioningToLargeClock(largeClockElements = largeWeatherClockElementKeyList)
+ }
+
+ from(
+ WeatherClockScenes.largeClockScene,
+ to = WeatherClockScenes.splitShadeLargeClockScene
+ ) {
+ spec = tween(300, easing = LinearEasing)
}
}
- private fun TransitionBuilder.transitioningToLargeClock() {
+ private fun TransitionBuilder.transitioningToLargeClock(largeClockElements: List<ElementKey>) {
spec = tween(durationMillis = STATUS_AREA_MOVE_UP_MILLIS.toInt())
timestampRange(
startMillis = CLOCK_IN_START_DELAY_MILLIS.toInt(),
endMillis = (CLOCK_IN_START_DELAY_MILLIS + CLOCK_IN_MILLIS).toInt()
) {
- fade(largeClockElementKey)
+ largeClockElements.forEach { fade(it) }
}
timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { fade(smallClockElementKey) }
anchoredTranslate(smallClockElementKey, smartspaceElementKey)
}
- private fun TransitionBuilder.transitioningToSmallClock() {
+ private fun TransitionBuilder.transitioningToSmallClock(largeClockElements: List<ElementKey>) {
spec = tween(durationMillis = STATUS_AREA_MOVE_DOWN_MILLIS.toInt())
timestampRange(
startMillis = CLOCK_IN_START_DELAY_MILLIS.toInt(),
@@ -66,7 +84,9 @@ object ClockTransition {
fade(smallClockElementKey)
}
- timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { fade(largeClockElementKey) }
+ timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) {
+ largeClockElements.forEach { fade(it) }
+ }
anchoredTranslate(smallClockElementKey, smartspaceElementKey)
}
}
@@ -81,14 +101,26 @@ object ClockScenes {
object ClockElementKeys {
val largeClockElementKey = ElementKey("large-clock")
val smallClockElementKey = ElementKey("small-clock")
- val weatherSmallClockElementKey = ElementKey("weather-small-clock")
val smartspaceElementKey = ElementKey("smart-space")
}
+object WeatherClockScenes {
+ val largeClockScene = SceneKey("large-weather-clock-scene")
+ val splitShadeLargeClockScene = SceneKey("split-shade-large-weather-clock-scene")
+}
+
object WeatherClockElementKeys {
val timeElementKey = ElementKey("weather-large-clock-time")
val dateElementKey = ElementKey("weather-large-clock-date")
val weatherIconElementKey = ElementKey("weather-large-clock-weather-icon")
val temperatureElementKey = ElementKey("weather-large-clock-temperature")
val dndAlarmElementKey = ElementKey("weather-large-clock-dnd-alarm")
+ val largeWeatherClockElementKeyList =
+ listOf(
+ timeElementKey,
+ dateElementKey,
+ weatherIconElementKey,
+ temperatureElementKey,
+ dndAlarmElementKey
+ )
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index c008a1a4d192..28e92aad914a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -86,16 +86,21 @@ constructor(
if (shouldUseSplitNotificationShade) {
with(notificationSection) {
Notifications(
- Modifier.fillMaxWidth(0.5f)
- .fillMaxHeight()
- .align(alignment = Alignment.TopEnd)
+ burnInParams = null,
+ modifier =
+ Modifier.fillMaxWidth(0.5f)
+ .fillMaxHeight()
+ .align(alignment = Alignment.TopEnd)
)
}
}
}
if (!shouldUseSplitNotificationShade) {
with(notificationSection) {
- Notifications(Modifier.weight(weight = 1f))
+ Notifications(
+ burnInParams = null,
+ modifier = Modifier.weight(weight = 1f)
+ )
}
}
if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 091a43923a6e..b8f00dce53df 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -86,16 +86,21 @@ constructor(
if (shouldUseSplitNotificationShade) {
with(notificationSection) {
Notifications(
- Modifier.fillMaxWidth(0.5f)
- .fillMaxHeight()
- .align(alignment = Alignment.TopEnd)
+ burnInParams = null,
+ modifier =
+ Modifier.fillMaxWidth(0.5f)
+ .fillMaxHeight()
+ .align(alignment = Alignment.TopEnd)
)
}
}
}
if (!shouldUseSplitNotificationShade) {
with(notificationSection) {
- Notifications(Modifier.weight(weight = 1f))
+ Notifications(
+ burnInParams = null,
+ modifier = Modifier.weight(weight = 1f)
+ )
}
}
if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
deleted file mode 100644
index 09d76a341442..000000000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
+++ /dev/null
@@ -1,497 +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.keyguard.ui.composable.blueprint
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.IntRect
-import androidx.compose.ui.unit.dp
-import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.padding
-import com.android.keyguard.KeyguardClockSwitch.LARGE
-import com.android.systemui.Flags
-import com.android.systemui.customization.R as customizationR
-import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
-import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
-import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
-import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
-import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
-import com.android.systemui.keyguard.ui.composable.section.LockSection
-import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
-import com.android.systemui.keyguard.ui.composable.section.NotificationSection
-import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
-import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
-import com.android.systemui.keyguard.ui.composable.section.WeatherClockSection
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.res.R
-import com.android.systemui.shade.LargeScreenHeaderHelper
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.IntoSet
-import java.util.Optional
-import javax.inject.Inject
-
-class WeatherClockBlueprint
-@Inject
-constructor(
- private val viewModel: LockscreenContentViewModel,
- private val statusBarSection: StatusBarSection,
- private val weatherClockSection: WeatherClockSection,
- private val smartSpaceSection: SmartSpaceSection,
- private val notificationSection: NotificationSection,
- private val lockSection: LockSection,
- private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
- private val bottomAreaSection: BottomAreaSection,
- private val settingsMenuSection: SettingsMenuSection,
- private val clockInteractor: KeyguardClockInteractor,
- private val mediaCarouselSection: MediaCarouselSection,
- private val clockViewModel: KeyguardClockViewModel,
-) : ComposableLockscreenSceneBlueprint {
-
- override val id: String = WEATHER_CLOCK_BLUEPRINT_ID
- @Composable
- override fun SceneScope.Content(modifier: Modifier) {
- val isUdfpsVisible = viewModel.isUdfpsVisible
- val burnIn = rememberBurnIn(clockInteractor)
- val resources = LocalContext.current.resources
- val currentClockState = clockViewModel.currentClock.collectAsState()
- val areNotificationsVisible by viewModel.areNotificationsVisible.collectAsState()
- LockscreenLongPress(
- viewModel = viewModel.longPress,
- modifier = modifier,
- ) { onSettingsMenuPlaced ->
- Layout(
- content = {
- // Constrained to above the lock icon.
- Column(
- modifier = Modifier.fillMaxWidth(),
- ) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- val currentClock = currentClockState.value
- val clockSize by clockViewModel.clockSize.collectAsState()
- with(weatherClockSection) {
- if (currentClock == null) {
- return@with
- }
-
- if (clockSize == LARGE) {
- Time(
- clock = currentClock,
- modifier =
- Modifier.padding(
- start =
- dimensionResource(
- customizationR.dimen.clock_padding_start
- )
- )
- )
- } else {
- SmallClock(
- burnInParams = burnIn.parameters,
- modifier =
- Modifier.align(Alignment.Start)
- .onTopPlacementChanged(burnIn.onSmallClockTopChanged),
- clock = currentClock
- )
- }
- }
- with(smartSpaceSection) {
- SmartSpace(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmartspaceTopChanged,
- modifier =
- Modifier.fillMaxWidth()
- .padding(
- top = { viewModel.getSmartSpacePaddingTop(resources) },
- )
- .padding(
- bottom =
- dimensionResource(
- R.dimen.keyguard_status_view_bottom_margin
- ),
- ),
- )
- }
-
- with(mediaCarouselSection) { MediaCarousel() }
-
- if (areNotificationsVisible) {
- with(notificationSection) {
- Notifications(
- modifier = Modifier.fillMaxWidth().weight(weight = 1f)
- )
- }
- }
- with(weatherClockSection) {
- if (currentClock == null || clockSize != LARGE) {
- return@with
- }
- LargeClockSectionBelowSmartspace(clock = currentClock)
- }
-
- if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
- with(ambientIndicationSectionOptional.get()) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
- }
-
- with(lockSection) { LockIcon() }
-
- // Aligned to bottom and constrained to below the lock icon.
- Column(modifier = Modifier.fillMaxWidth()) {
- if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
- with(ambientIndicationSectionOptional.get()) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
-
- with(bottomAreaSection) {
- IndicationArea(modifier = Modifier.fillMaxWidth())
- }
- }
-
- // Aligned to bottom and NOT constrained by the lock icon.
- with(bottomAreaSection) {
- Shortcut(isStart = true, applyPadding = true)
- Shortcut(isStart = false, applyPadding = true)
- }
- with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
- },
- modifier = Modifier.fillMaxSize(),
- ) { measurables, constraints ->
- check(measurables.size == 6)
- val aboveLockIconMeasurable = measurables[0]
- val lockIconMeasurable = measurables[1]
- val belowLockIconMeasurable = measurables[2]
- val startShortcutMeasurable = measurables[3]
- val endShortcutMeasurable = measurables[4]
- val settingsMenuMeasurable = measurables[5]
-
- val noMinConstraints =
- constraints.copy(
- minWidth = 0,
- minHeight = 0,
- )
- val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
- val lockIconBounds =
- IntRect(
- left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
- top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
- right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
- bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
- )
-
- val aboveLockIconPlaceable =
- aboveLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = lockIconBounds.top)
- )
- val belowLockIconPlaceable =
- belowLockIconMeasurable.measure(
- noMinConstraints.copy(
- maxHeight =
- (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
- )
- )
- val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
- val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
- val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
-
- layout(constraints.maxWidth, constraints.maxHeight) {
- aboveLockIconPlaceable.place(
- x = 0,
- y = 0,
- )
- lockIconPlaceable.place(
- x = lockIconBounds.left,
- y = lockIconBounds.top,
- )
- belowLockIconPlaceable.place(
- x = 0,
- y = constraints.maxHeight - belowLockIconPlaceable.height,
- )
- startShortcutPleaceable.place(
- x = 0,
- y = constraints.maxHeight - startShortcutPleaceable.height,
- )
- endShortcutPleaceable.place(
- x = constraints.maxWidth - endShortcutPleaceable.width,
- y = constraints.maxHeight - endShortcutPleaceable.height,
- )
- settingsMenuPlaceable.place(
- x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
- y = constraints.maxHeight - settingsMenuPlaceable.height,
- )
- }
- }
- }
- }
-}
-
-class SplitShadeWeatherClockBlueprint
-@Inject
-constructor(
- private val viewModel: LockscreenContentViewModel,
- private val statusBarSection: StatusBarSection,
- private val smartSpaceSection: SmartSpaceSection,
- private val notificationSection: NotificationSection,
- private val lockSection: LockSection,
- private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
- private val bottomAreaSection: BottomAreaSection,
- private val settingsMenuSection: SettingsMenuSection,
- private val clockInteractor: KeyguardClockInteractor,
- private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
- private val weatherClockSection: WeatherClockSection,
- private val mediaCarouselSection: MediaCarouselSection,
- private val clockViewModel: KeyguardClockViewModel,
-) : ComposableLockscreenSceneBlueprint {
- override val id: String = SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
-
- @Composable
- override fun SceneScope.Content(modifier: Modifier) {
- val isUdfpsVisible = viewModel.isUdfpsVisible
- val burnIn = rememberBurnIn(clockInteractor)
- val resources = LocalContext.current.resources
- val currentClockState = clockViewModel.currentClock.collectAsState()
- LockscreenLongPress(
- viewModel = viewModel.longPress,
- modifier = modifier,
- ) { onSettingsMenuPlaced ->
- Layout(
- content = {
- // Constrained to above the lock icon.
- Column(
- modifier = Modifier.fillMaxSize(),
- ) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- Row(
- modifier = Modifier.fillMaxSize(),
- ) {
- Column(
- modifier = Modifier.fillMaxHeight().weight(weight = 1f),
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- val currentClock = currentClockState.value
- val clockSize by clockViewModel.clockSize.collectAsState()
- with(weatherClockSection) {
- if (currentClock == null) {
- return@with
- }
-
- if (clockSize == LARGE) {
- Time(
- clock = currentClock,
- modifier =
- Modifier.align(Alignment.Start)
- .padding(
- start =
- dimensionResource(
- customizationR.dimen
- .clock_padding_start
- )
- )
- )
- } else {
- SmallClock(
- burnInParams = burnIn.parameters,
- modifier =
- Modifier.align(Alignment.Start)
- .onTopPlacementChanged(
- burnIn.onSmallClockTopChanged
- ),
- clock = currentClock,
- )
- }
- }
- with(smartSpaceSection) {
- SmartSpace(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmartspaceTopChanged,
- modifier =
- Modifier.fillMaxWidth()
- .padding(
- top = {
- viewModel.getSmartSpacePaddingTop(resources)
- },
- )
- .padding(
- bottom =
- dimensionResource(
- R.dimen
- .keyguard_status_view_bottom_margin
- )
- ),
- )
- }
-
- with(mediaCarouselSection) { MediaCarousel() }
-
- with(weatherClockSection) {
- if (currentClock == null || clockSize != LARGE) {
- return@with
- }
-
- LargeClockSectionBelowSmartspace(currentClock)
- }
- }
- with(notificationSection) {
- val splitShadeTopMargin: Dp =
- if (Flags.centralizedStatusBarHeightFix()) {
- largeScreenHeaderHelper.getLargeScreenHeaderHeight().dp
- } else {
- dimensionResource(
- id = R.dimen.large_screen_shade_header_height
- )
- }
- Notifications(
- modifier =
- Modifier.fillMaxHeight()
- .weight(weight = 1f)
- .padding(top = splitShadeTopMargin)
- )
- }
- }
-
- if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
- with(ambientIndicationSectionOptional.get()) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
- }
-
- with(lockSection) { LockIcon() }
-
- // Aligned to bottom and constrained to below the lock icon.
- Column(modifier = Modifier.fillMaxWidth()) {
- if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
- with(ambientIndicationSectionOptional.get()) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
-
- with(bottomAreaSection) {
- IndicationArea(modifier = Modifier.fillMaxWidth())
- }
- }
-
- // Aligned to bottom and NOT constrained by the lock icon.
- with(bottomAreaSection) {
- Shortcut(isStart = true, applyPadding = true)
- Shortcut(isStart = false, applyPadding = true)
- }
- with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
- },
- modifier = Modifier.fillMaxSize(),
- ) { measurables, constraints ->
- check(measurables.size == 6)
- val aboveLockIconMeasurable = measurables[0]
- val lockIconMeasurable = measurables[1]
- val belowLockIconMeasurable = measurables[2]
- val startShortcutMeasurable = measurables[3]
- val endShortcutMeasurable = measurables[4]
- val settingsMenuMeasurable = measurables[5]
-
- val noMinConstraints =
- constraints.copy(
- minWidth = 0,
- minHeight = 0,
- )
- val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
- val lockIconBounds =
- IntRect(
- left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
- top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
- right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
- bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
- )
-
- val aboveLockIconPlaceable =
- aboveLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = lockIconBounds.top)
- )
- val belowLockIconPlaceable =
- belowLockIconMeasurable.measure(
- noMinConstraints.copy(
- maxHeight =
- (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
- )
- )
- val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
- val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
- val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
-
- layout(constraints.maxWidth, constraints.maxHeight) {
- aboveLockIconPlaceable.place(
- x = 0,
- y = 0,
- )
- lockIconPlaceable.place(
- x = lockIconBounds.left,
- y = lockIconBounds.top,
- )
- belowLockIconPlaceable.place(
- x = 0,
- y = constraints.maxHeight - belowLockIconPlaceable.height,
- )
- startShortcutPleaceable.place(
- x = 0,
- y = constraints.maxHeight - startShortcutPleaceable.height,
- )
- endShortcutPleaceable.place(
- x = constraints.maxWidth - endShortcutPleaceable.width,
- y = constraints.maxHeight - endShortcutPleaceable.height,
- )
- settingsMenuPlaceable.place(
- x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
- y = constraints.maxHeight - settingsMenuPlaceable.height,
- )
- }
- }
- }
- }
-}
-
-@Module
-interface WeatherClockBlueprintModule {
- @Binds
- @IntoSet
- fun blueprint(blueprint: WeatherClockBlueprint): ComposableLockscreenSceneBlueprint
-}
-
-@Module
-interface SplitShadeWeatherClockBlueprintModule {
- @Binds
- @IntoSet
- fun blueprint(blueprint: SplitShadeWeatherClockBlueprint): ComposableLockscreenSceneBlueprint
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 467dbca759c8..97d5b41000de 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -32,7 +32,6 @@ import androidx.core.content.res.ResourcesCompat
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.animation.view.LaunchableImageView
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
@@ -44,7 +43,6 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.flow.Flow
@@ -56,7 +54,6 @@ constructor(
private val vibratorHelper: VibratorHelper,
private val indicationController: KeyguardIndicationController,
private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
- @Main private val mainImmediateDispatcher: CoroutineDispatcher,
) {
/**
* Renders a single lockscreen shortcut.
@@ -164,7 +161,6 @@ constructor(
transitionAlpha,
falsingManager,
vibratorHelper,
- mainImmediateDispatcher,
) {
indicationController.showTransientIndication(it)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index 48684a02bd19..9f02201f1d81 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -34,7 +34,6 @@ import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
@@ -51,14 +50,12 @@ import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
class LockSection
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- @Main private val mainImmediateDispatcher: CoroutineDispatcher,
private val windowManager: WindowManager,
private val authController: AuthController,
private val featureFlags: FeatureFlagsClassic,
@@ -96,7 +93,6 @@ constructor(
deviceEntryBackgroundViewModel.get(),
falsingManager.get(),
vibratorHelper.get(),
- mainImmediateDispatcher,
)
}
} else {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index a31b53383014..f48fa88b9722 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -32,6 +32,9 @@ import com.android.compose.modifiers.thenIf
import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.MigrateClocksToBlueprint
+import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack
import com.android.systemui.res.R
@@ -48,6 +51,7 @@ class NotificationSection
@Inject
constructor(
private val viewModel: NotificationsPlaceholderViewModel,
+ private val aodBurnInViewModel: AodBurnInViewModel,
sharedNotificationContainer: SharedNotificationContainer,
sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
stackScrollLayout: NotificationStackScrollLayout,
@@ -77,8 +81,12 @@ constructor(
)
}
+ /**
+ * @param burnInParams params to make this view adaptive to burn-in, `null` to disable burn-in
+ * adjustment
+ */
@Composable
- fun SceneScope.Notifications(modifier: Modifier = Modifier) {
+ fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) {
val shouldUseSplitNotificationShade by
lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsState()
val areNotificationsVisible by
@@ -97,9 +105,21 @@ constructor(
ConstrainedNotificationStack(
viewModel = viewModel,
modifier =
- modifier.fillMaxWidth().thenIf(shouldUseSplitNotificationShade) {
- Modifier.padding(top = splitShadeTopMargin)
- },
+ modifier
+ .fillMaxWidth()
+ .thenIf(shouldUseSplitNotificationShade) {
+ Modifier.padding(top = splitShadeTopMargin)
+ }
+ .let {
+ if (burnInParams == null) {
+ it
+ } else {
+ it.burnInAware(
+ viewModel = aodBurnInViewModel,
+ params = burnInParams,
+ )
+ }
+ },
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index f8e63411ed52..0934b20562b4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -16,9 +16,11 @@
package com.android.systemui.keyguard.ui.composable.section
+import android.content.Context
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.Composable
@@ -26,6 +28,10 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.SceneTransitionLayout
@@ -36,6 +42,7 @@ import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallCl
import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeLargeClockScene
import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeSmallClockScene
import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition
+import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockScenes
import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import javax.inject.Inject
@@ -47,6 +54,7 @@ constructor(
private val smartSpaceSection: SmartSpaceSection,
private val mediaCarouselSection: MediaCarouselSection,
private val clockSection: DefaultClockSection,
+ private val weatherClockSection: WeatherClockSection,
private val clockInteractor: KeyguardClockInteractor,
) {
@Composable
@@ -64,6 +72,10 @@ constructor(
splitShadeSmallClockScene
KeyguardClockViewModel.ClockLayout.LARGE_CLOCK -> largeClockScene
KeyguardClockViewModel.ClockLayout.SMALL_CLOCK -> smallClockScene
+ KeyguardClockViewModel.ClockLayout.WEATHER_LARGE_CLOCK ->
+ WeatherClockScenes.largeClockScene
+ KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_WEATHER_LARGE_CLOCK ->
+ WeatherClockScenes.splitShadeLargeClockScene
}
SceneTransitionLayout(
@@ -86,6 +98,12 @@ constructor(
scene(smallClockScene) { SmallClockWithSmartSpace() }
scene(largeClockScene) { LargeClockWithSmartSpace() }
+
+ scene(WeatherClockScenes.largeClockScene) { WeatherLargeClockWithSmartSpace() }
+
+ scene(WeatherClockScenes.splitShadeLargeClockScene) {
+ WeatherLargeClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
+ }
}
}
@@ -146,4 +164,50 @@ constructor(
}
}
}
+
+ @Composable
+ private fun SceneScope.WeatherLargeClockWithSmartSpace(modifier: Modifier = Modifier) {
+ val burnIn = rememberBurnIn(clockInteractor)
+ val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState()
+ val currentClockState = clockViewModel.currentClock.collectAsState()
+
+ LaunchedEffect(isLargeClockVisible) {
+ if (isLargeClockVisible) {
+ burnIn.onSmallClockTopChanged(null)
+ }
+ }
+
+ Column(modifier = modifier) {
+ val currentClock = currentClockState.value ?: return@Column
+ with(weatherClockSection) { Time(clock = currentClock, modifier = Modifier) }
+ val density = LocalDensity.current
+ val context = LocalContext.current
+
+ with(smartSpaceSection) {
+ SmartSpace(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmartspaceTopChanged,
+ modifier =
+ Modifier.heightIn(
+ min = getDimen(context, "enhanced_smartspace_height", density)
+ )
+ )
+ }
+ with(weatherClockSection) { LargeClockSectionBelowSmartspace(clock = currentClock) }
+ }
+ }
+
+ /*
+ * Use this function to access dimen which cannot be access by R.dimen directly
+ * Currently use to access dimen from BcSmartspace
+ * @param name Name of resources
+ * @param density Density required to convert dimen from Int To Dp
+ */
+ private fun getDimen(context: Context, name: String, density: Density): Dp {
+ val res = context.packageManager.getResourcesForApplication(context.packageName)
+ val id = res.getIdentifier(name, "dimen", context.packageName)
+ var dimen: Dp
+ with(density) { dimen = (if (id == 0) 0 else res.getDimensionPixelSize(id)).toDp() }
+ return dimen
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt
index d3584539b3fa..a7bb308ada5c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.composable.section
+import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.IntrinsicSize
@@ -27,18 +28,14 @@ import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
-import com.android.systemui.customization.R
-import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.weatherSmallClockElementKey
+import com.android.systemui.customization.R as customizationR
import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockElementKeys
-import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
-import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.plugins.clocks.ClockController
import javax.inject.Inject
@@ -55,12 +52,19 @@ constructor(
clock: ClockController,
modifier: Modifier = Modifier,
) {
- WeatherElement(
- weatherClockElementViewId = R.id.weather_clock_time,
- clock = clock,
- elementKey = WeatherClockElementKeys.timeElementKey,
- modifier = modifier.wrapContentSize(),
- )
+ Row(
+ modifier =
+ Modifier.padding(
+ horizontal = dimensionResource(customizationR.dimen.clock_padding_start)
+ )
+ ) {
+ WeatherElement(
+ weatherClockElementViewId = customizationR.id.weather_clock_time,
+ clock = clock,
+ elementKey = WeatherClockElementKeys.timeElementKey,
+ modifier = modifier,
+ )
+ }
}
@Composable
@@ -69,7 +73,7 @@ constructor(
modifier: Modifier = Modifier,
) {
WeatherElement(
- weatherClockElementViewId = R.id.weather_clock_date,
+ weatherClockElementViewId = customizationR.id.weather_clock_date,
clock = clock,
elementKey = WeatherClockElementKeys.dateElementKey,
modifier = modifier,
@@ -82,7 +86,7 @@ constructor(
modifier: Modifier = Modifier,
) {
WeatherElement(
- weatherClockElementViewId = R.id.weather_clock_weather_icon,
+ weatherClockElementViewId = customizationR.id.weather_clock_weather_icon,
clock = clock,
elementKey = WeatherClockElementKeys.weatherIconElementKey,
modifier = modifier.wrapContentSize(),
@@ -95,7 +99,7 @@ constructor(
modifier: Modifier = Modifier,
) {
WeatherElement(
- weatherClockElementViewId = R.id.weather_clock_alarm_dnd,
+ weatherClockElementViewId = customizationR.id.weather_clock_alarm_dnd,
clock = clock,
elementKey = WeatherClockElementKeys.dndAlarmElementKey,
modifier = modifier.wrapContentSize(),
@@ -108,7 +112,7 @@ constructor(
modifier: Modifier = Modifier,
) {
WeatherElement(
- weatherClockElementViewId = R.id.weather_clock_temperature,
+ weatherClockElementViewId = customizationR.id.weather_clock_temperature,
clock = clock,
elementKey = WeatherClockElementKeys.temperatureElementKey,
modifier = modifier.wrapContentSize(),
@@ -126,12 +130,16 @@ constructor(
content {
AndroidView(
factory = {
- val view =
- clock.largeClock.layout.views.first {
- it.id == weatherClockElementViewId
- }
- (view.parent as? ViewGroup)?.removeView(view)
- view
+ try {
+ val view =
+ clock.largeClock.layout.views.first {
+ it.id == weatherClockElementViewId
+ }
+ (view.parent as? ViewGroup)?.removeView(view)
+ view
+ } catch (e: NoSuchElementException) {
+ View(it)
+ }
},
update = {},
modifier = modifier
@@ -147,46 +155,22 @@ constructor(
Row(
modifier =
Modifier.height(IntrinsicSize.Max)
- .padding(horizontal = dimensionResource(R.dimen.clock_padding_start))
+ .padding(
+ horizontal = dimensionResource(customizationR.dimen.clock_padding_start)
+ )
) {
Date(clock = clock, modifier = Modifier.wrapContentSize())
- Box(modifier = Modifier.fillMaxSize()) {
+ Box(
+ modifier =
+ Modifier.fillMaxSize()
+ .padding(
+ start = dimensionResource(customizationR.dimen.clock_padding_start)
+ )
+ ) {
Weather(clock = clock, modifier = Modifier.align(Alignment.TopStart))
Temperature(clock = clock, modifier = Modifier.align(Alignment.BottomEnd))
DndAlarmStatus(clock = clock, modifier = Modifier.align(Alignment.TopEnd))
}
}
}
-
- @Composable
- fun SceneScope.SmallClock(
- burnInParams: BurnInParameters,
- modifier: Modifier = Modifier,
- clock: ClockController,
- ) {
- val localContext = LocalContext.current
- MovableElement(key = weatherSmallClockElementKey, modifier) {
- content {
- AndroidView(
- factory = {
- val view = clock.smallClock.view
- if (view.parent != null) {
- (view.parent as? ViewGroup)?.removeView(view)
- }
- view
- },
- modifier =
- modifier
- .height(dimensionResource(R.dimen.small_clock_height))
- .padding(start = dimensionResource(R.dimen.clock_padding_start))
- .padding(top = { viewModel.getSmallClockTopMargin(localContext) })
- .burnInAware(
- viewModel = aodBurnInViewModel,
- params = burnInParams,
- ),
- update = {},
- )
- }
- }
- }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
new file mode 100644
index 000000000000..ca6b3434d90e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.ui.composable
+
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.modifiers.height
+import com.android.compose.modifiers.width
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.settings.brightness.ui.binder.BrightnessMirrorInflater
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+
+@Composable
+fun BrightnessMirror(
+ viewModel: BrightnessMirrorViewModel,
+ qsSceneAdapter: QSSceneAdapter,
+ modifier: Modifier = Modifier,
+) {
+ val isShowing by viewModel.isShowing.collectAsState()
+ val mirrorAlpha by
+ animateFloatAsState(
+ targetValue = if (isShowing) 1f else 0f,
+ label = "alphaAnimationBrightnessMirrorShowing",
+ )
+ val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsState()
+ val offset = IntOffset(0, mirrorOffsetAndSize.yOffset)
+
+ Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = mirrorAlpha }) {
+ QuickSettingsTheme {
+ // The assumption for using this AndroidView is that there will be only one in view at
+ // a given time (which is a reasonable assumption). Because `QSSceneAdapter` (actually
+ // `BrightnessSliderController` only supports a single mirror).
+ // The benefit of doing it like this is that if the configuration changes or QSImpl is
+ // re-inflated, it's not relevant to the composable, as we'll always get a new one.
+ AndroidView(
+ modifier =
+ Modifier.align(Alignment.TopCenter)
+ .offset { offset }
+ .width { mirrorOffsetAndSize.width }
+ .height { mirrorOffsetAndSize.height },
+ factory = { context ->
+ val (view, controller) =
+ BrightnessMirrorInflater.inflate(context, viewModel.sliderControllerFactory)
+ viewModel.setToggleSlider(controller)
+ view
+ },
+ update = { qsSceneAdapter.setBrightnessMirrorController(viewModel) },
+ onRelease = { qsSceneAdapter.setBrightnessMirrorController(null) }
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 244f48019dc5..a3768346e7c6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -19,6 +19,7 @@ package com.android.systemui.qs.ui.composable
import android.view.ViewGroup
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
@@ -49,6 +50,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.colorResource
@@ -120,8 +122,20 @@ private fun SceneScope.QuickSettingsScene(
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
+ val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
+ val contentAlpha by
+ animateFloatAsState(
+ targetValue = if (brightnessMirrorShowing) 0f else 1f,
+ label = "alphaAnimationBrightnessMirrorContentHiding",
+ )
+
+ BrightnessMirror(
+ viewModel = viewModel.brightnessMirrorViewModel,
+ qsSceneAdapter = viewModel.qsSceneAdapter
+ )
+
// TODO(b/280887232): implement the real UI.
- Box(modifier = modifier.fillMaxSize()) {
+ Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = contentAlpha }) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
BackHandler(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index fcd77609768e..02a12e4e0814 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -54,9 +54,9 @@ import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.ValueKey
import com.android.compose.animation.scene.animateElementFloatAsState
-import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.settingslib.Utils
import com.android.systemui.battery.BatteryMeterView
@@ -87,10 +87,6 @@ object ShadeHeader {
val ShadeCarrierGroup = ElementKey("ShadeCarrierGroup")
}
- object Keys {
- val transitionProgress = ValueKey("ShadeHeaderTransitionProgress")
- }
-
object Values {
val ClockScale = ValueKey("ShadeHeaderClockScale")
}
@@ -119,19 +115,17 @@ fun SceneScope.CollapsedShadeHeader(
return
}
- val formatProgress =
- animateSceneFloatAsState(0f, ShadeHeader.Keys.transitionProgress)
- .unsafeCompositionState(initialValue = 0f)
-
val cutoutWidth = LocalDisplayCutout.current.width()
val cutoutLocation = LocalDisplayCutout.current.location
val useExpandedFormat by
- remember(formatProgress) {
+ remember(cutoutLocation) {
derivedStateOf {
- cutoutLocation != CutoutLocation.CENTER || formatProgress.value > 0.5f
+ cutoutLocation != CutoutLocation.CENTER ||
+ shouldUseExpandedFormat(layoutState.transitionState)
}
}
+
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
// This layout assumes it is globally positioned at (0, 0) and is the
@@ -207,7 +201,7 @@ fun SceneScope.CollapsedShadeHeader(
val screenWidth = constraints.maxWidth
val cutoutWidthPx = cutoutWidth.roundToPx()
- val height = ShadeHeader.Dimensions.CollapsedHeight.roundToPx()
+ val height = CollapsedHeight.roundToPx()
val childConstraints = Constraints.fixed((screenWidth - cutoutWidthPx) / 2, height)
val startMeasurable = measurables[0][0]
@@ -261,11 +255,10 @@ fun SceneScope.ExpandedShadeHeader(
return
}
- val formatProgress =
- animateSceneFloatAsState(1f, ShadeHeader.Keys.transitionProgress)
- .unsafeCompositionState(initialValue = 1f)
- val useExpandedFormat by
- remember(formatProgress) { derivedStateOf { formatProgress.value > 0.5f } }
+ val useExpandedFormat by remember {
+ derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) }
+ }
+
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
Box(modifier = modifier) {
@@ -530,3 +523,15 @@ private fun SceneScope.PrivacyChip(
modifier = modifier.element(ShadeHeader.Elements.PrivacyChip),
)
}
+
+private fun shouldUseExpandedFormat(state: TransitionState): Boolean {
+ return when (state) {
+ is TransitionState.Idle -> {
+ state.currentScene == Scenes.QuickSettings
+ }
+ is TransitionState.Transition -> {
+ (state.isTransitioning(Scenes.Shade, Scenes.QuickSettings) && state.progress >= 0.5) ||
+ (state.isTransitioning(Scenes.QuickSettings, Scenes.Shade) && state.progress < 0.5)
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 85798acd0dcd..9bd6f817cff3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.ui.composable
import android.view.ViewGroup
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.clipScrollableContainer
@@ -33,6 +34,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -45,6 +47,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalDensity
@@ -70,6 +73,7 @@ import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
+import com.android.systemui.qs.ui.composable.BrightnessMirror
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
@@ -302,12 +306,25 @@ private fun SceneScope.SplitShade(
}
}
+ val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
+ val contentAlpha by
+ animateFloatAsState(
+ targetValue = if (brightnessMirrorShowing) 0f else 1f,
+ label = "alphaAnimationBrightnessMirrorContentHiding",
+ )
+
+ val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha }
+
Box(
modifier =
modifier
.fillMaxSize()
.element(Shade.Elements.BackgroundScrim)
- .background(colorResource(R.color.shade_scrim_background_dark))
+ // Cannot set the alpha of the whole element to 0, because the mirror should be
+ // in the QS column.
+ .background(
+ colorResource(R.color.shade_scrim_background_dark).copy(alpha = contentAlpha)
+ )
) {
Column(
modifier = Modifier.fillMaxSize(),
@@ -317,61 +334,80 @@ private fun SceneScope.SplitShade(
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
- modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
+ modifier =
+ Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
+ .then(brightnessMirrorShowingModifier)
)
Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
- Column(
- verticalArrangement = Arrangement.Top,
- modifier =
- Modifier.weight(1f).fillMaxSize().thenIf(!isCustomizing) {
- Modifier.padding(bottom = navBarBottomHeight)
- },
- ) {
+ Box(modifier = Modifier.weight(1f)) {
+ BrightnessMirror(
+ viewModel = viewModel.brightnessMirrorViewModel,
+ qsSceneAdapter = viewModel.qsSceneAdapter,
+ // Need to remove the offset of the header height, as the mirror uses
+ // the position of the Brightness slider in the window
+ modifier = Modifier.offset(y = -ShadeHeader.Dimensions.CollapsedHeight)
+ )
Column(
+ verticalArrangement = Arrangement.Top,
modifier =
- Modifier.fillMaxSize().weight(1f).thenIf(!isCustomizing) {
- Modifier.verticalNestedScrollToScene()
- .verticalScroll(
- quickSettingsScrollState,
- enabled = isScrollable
- )
- .clipScrollableContainer(Orientation.Horizontal)
- }
+ Modifier.fillMaxSize().thenIf(!isCustomizing) {
+ Modifier.padding(bottom = navBarBottomHeight)
+ },
) {
- Box(
+ Column(
modifier =
- Modifier.element(QuickSettings.Elements.SplitShadeQuickSettings)
+ Modifier.fillMaxSize()
+ .weight(1f)
+ .thenIf(!isCustomizing) {
+ Modifier.verticalNestedScrollToScene()
+ .verticalScroll(
+ quickSettingsScrollState,
+ enabled = isScrollable
+ )
+ .clipScrollableContainer(Orientation.Horizontal)
+ }
+ .then(brightnessMirrorShowingModifier)
) {
- QuickSettings(
- qsSceneAdapter = viewModel.qsSceneAdapter,
- heightProvider = { viewModel.qsSceneAdapter.qsHeight },
- isSplitShade = true,
+ Box(
+ modifier =
+ Modifier.element(QuickSettings.Elements.SplitShadeQuickSettings)
+ ) {
+ QuickSettings(
+ qsSceneAdapter = viewModel.qsSceneAdapter,
+ heightProvider = { viewModel.qsSceneAdapter.qsHeight },
+ isSplitShade = true,
+ modifier = Modifier.fillMaxWidth(),
+ squishiness = tileSquishiness,
+ )
+ }
+
+ MediaIfVisible(
+ viewModel = viewModel,
+ mediaCarouselController = mediaCarouselController,
+ mediaHost = mediaHost,
modifier = Modifier.fillMaxWidth(),
- squishiness = tileSquishiness,
)
}
-
- MediaIfVisible(
- viewModel = viewModel,
- mediaCarouselController = mediaCarouselController,
- mediaHost = mediaHost,
- modifier = Modifier.fillMaxWidth(),
+ FooterActionsWithAnimatedVisibility(
+ viewModel = footerActionsViewModel,
+ isCustomizing = isCustomizing,
+ lifecycleOwner = lifecycleOwner,
+ modifier =
+ Modifier.align(Alignment.CenterHorizontally)
+ .then(brightnessMirrorShowingModifier),
)
}
- FooterActionsWithAnimatedVisibility(
- viewModel = footerActionsViewModel,
- isCustomizing = isCustomizing,
- lifecycleOwner = lifecycleOwner,
- modifier = Modifier.align(Alignment.CenterHorizontally),
- )
}
NotificationScrollingStack(
viewModel = viewModel.notifications,
maxScrimTop = { 0f },
modifier =
- Modifier.weight(1f).fillMaxHeight().padding(bottom = navBarBottomHeight),
+ Modifier.weight(1f)
+ .fillMaxHeight()
+ .padding(bottom = navBarBottomHeight)
+ .then(brightnessMirrorShowingModifier),
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt
index ccb5d367c357..fa052e8e3035 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt
@@ -17,15 +17,12 @@
package com.android.systemui.volume.panel.component.anc
import com.android.systemui.volume.panel.component.anc.domain.AncAvailabilityCriteria
-import com.android.systemui.volume.panel.component.anc.ui.composable.AncPopup
-import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel
-import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
+import com.android.systemui.volume.panel.component.anc.ui.composable.AncButtonComponent
import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
import dagger.Binds
import dagger.Module
-import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey
@@ -40,14 +37,8 @@ interface AncModule {
criteria: AncAvailabilityCriteria
): ComponentAvailabilityCriteria
- companion object {
-
- @Provides
- @IntoMap
- @StringKey(VolumePanelComponents.ANC)
- fun provideVolumePanelUiComponent(
- viewModel: AncViewModel,
- popup: AncPopup,
- ): VolumePanelUiComponent = ButtonComponent(viewModel.button, popup::show)
- }
+ @Binds
+ @IntoMap
+ @StringKey(VolumePanelComponents.ANC)
+ fun bindVolumePanelUiComponent(component: AncButtonComponent): VolumePanelUiComponent
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
new file mode 100644
index 000000000000..00225fc3577a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.panel.component.anc.ui.composable
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel
+import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
+import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
+import javax.inject.Inject
+
+class AncButtonComponent
+@Inject
+constructor(
+ private val viewModel: AncViewModel,
+ private val ancPopup: AncPopup,
+) : ComposeVolumePanelUiComponent {
+
+ @Composable
+ override fun VolumePanelComposeScope.Content(modifier: Modifier) {
+ val slice by viewModel.buttonSlice.collectAsState()
+ val label = stringResource(R.string.volume_panel_noise_control_title)
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(12.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ SliceAndroidView(
+ modifier =
+ Modifier.height(64.dp)
+ .fillMaxWidth()
+ .semantics {
+ role = Role.Button
+ contentDescription = label
+ }
+ .clip(RoundedCornerShape(28.dp)),
+ slice = slice,
+ onWidthChanged = viewModel::onButtonSliceWidthChanged,
+ onClick = { ancPopup.show(null) }
+ )
+ Text(
+ modifier = Modifier.clearAndSetSemantics {},
+ text = label,
+ style = MaterialTheme.typography.labelMedium,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index 9f0da004730d..e1ee01e78566 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -16,9 +16,6 @@
package com.android.systemui.volume.panel.component.anc.ui.composable
-import android.content.Context
-import android.view.ContextThemeWrapper
-import android.view.View
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
@@ -30,14 +27,14 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.viewinterop.AndroidView
import androidx.slice.Slice
-import androidx.slice.widget.SliceView
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel
import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
/** ANC popup up displaying ANC control [Slice]. */
@@ -46,10 +43,12 @@ class AncPopup
constructor(
private val volumePanelPopup: VolumePanelPopup,
private val viewModel: AncViewModel,
+ private val uiEventLogger: UiEventLogger,
) {
/** Shows a popup with the [expandable] animation. */
- fun show(expandable: Expandable) {
+ fun show(expandable: Expandable?) {
+ uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_ANC_POPUP_SHOWN)
volumePanelPopup.show(expandable, { Title() }, { Content(it) })
}
@@ -66,49 +65,18 @@ constructor(
@Composable
private fun Content(dialog: SystemUIDialog) {
- val slice: Slice? by viewModel.slice.collectAsState()
+ val isAvailable by viewModel.isAvailable.collectAsState(true)
- if (slice == null) {
+ if (!isAvailable) {
SideEffect { dialog.dismiss() }
return
}
- AndroidView<SliceView>(
+ val slice by viewModel.popupSlice.collectAsState()
+ SliceAndroidView(
modifier = Modifier.fillMaxWidth(),
- factory = { context: Context ->
- SliceView(ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel))
- .apply {
- mode = SliceView.MODE_LARGE
- isScrollable = false
- importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
- setShowTitleItems(true)
- addOnLayoutChangeListener(
- OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
- )
- }
- },
- update = { sliceView: SliceView -> sliceView.slice = slice }
+ slice = slice,
+ onWidthChanged = viewModel::onPopupSliceWidthChanged
)
}
-
- private class OnWidthChangedLayoutListener(private val widthChanged: (Int) -> Unit) :
- View.OnLayoutChangeListener {
- override fun onLayoutChange(
- v: View?,
- left: Int,
- top: Int,
- right: Int,
- bottom: Int,
- oldLeft: Int,
- oldTop: Int,
- oldRight: Int,
- oldBottom: Int
- ) {
- val newWidth = right - left
- val oldWidth = oldRight - oldLeft
- if (oldWidth != newWidth) {
- widthChanged(newWidth)
- }
- }
- }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
new file mode 100644
index 000000000000..f354b80692f5
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.panel.component.anc.ui.composable
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.ContextThemeWrapper
+import android.view.MotionEvent
+import android.view.View
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.slice.Slice
+import androidx.slice.widget.SliceView
+import com.android.systemui.res.R
+
+@Composable
+fun SliceAndroidView(
+ slice: Slice?,
+ modifier: Modifier = Modifier,
+ onWidthChanged: ((Int) -> Unit)? = null,
+ onClick: (() -> Unit)? = null,
+) {
+ AndroidView(
+ modifier = modifier,
+ factory = { context: Context ->
+ ClickableSliceView(
+ ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel),
+ onClick,
+ )
+ .apply {
+ mode = SliceView.MODE_LARGE
+ isScrollable = false
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ setShowTitleItems(true)
+ if (onWidthChanged != null) {
+ addOnLayoutChangeListener(OnWidthChangedLayoutListener(onWidthChanged))
+ }
+ if (onClick != null) {
+ setOnClickListener { onClick() }
+ }
+ }
+ },
+ update = { sliceView: SliceView -> sliceView.slice = slice }
+ )
+}
+
+class OnWidthChangedLayoutListener(private val widthChanged: (Int) -> Unit) :
+ View.OnLayoutChangeListener {
+
+ override fun onLayoutChange(
+ v: View?,
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ oldLeft: Int,
+ oldTop: Int,
+ oldRight: Int,
+ oldBottom: Int
+ ) {
+ val newWidth = right - left
+ val oldWidth = oldRight - oldLeft
+ if (oldWidth != newWidth) {
+ widthChanged(newWidth)
+ }
+ }
+}
+
+/**
+ * [SliceView] that prioritises [onClick] when its clicked instead of passing the event to the slice
+ * first.
+ */
+@SuppressLint("ViewConstructor") // only used in this class
+private class ClickableSliceView(
+ context: Context,
+ private val onClick: (() -> Unit)?,
+) : SliceView(context) {
+
+ init {
+ if (onClick != null) {
+ setOnClickListener {}
+ }
+ }
+
+ override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
+ return onClick != null || super.onInterceptTouchEvent(ev)
+ }
+
+ override fun onClick(v: View?) {
+ onClick?.let { it() } ?: super.onClick(v)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt
new file mode 100644
index 000000000000..167eb65da7ee
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.button.ui.composable
+
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+/**
+ * Container to create a rim around the button. Both `Expandable` and `OutlinedIconToggleButton`
+ * have antialiasing problem when used with [androidx.compose.foundation.BorderStroke] and some
+ * contrast container color.
+ */
+// TODO(b/331584069): Remove this once antialiasing bug is fixed
+@Composable
+fun BottomComponentButtonSurface(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
+ Surface(
+ modifier = modifier.height(64.dp),
+ shape = RoundedCornerShape(28.dp),
+ color = MaterialTheme.colorScheme.surface,
+ content = content,
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index 8f187cce9e77..fc511e12ec54 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -16,14 +16,12 @@
package com.android.systemui.volume.panel.component.button.ui.composable
-import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
@@ -64,20 +62,21 @@ class ButtonComponent(
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
- Expandable(
- modifier =
- Modifier.height(64.dp).fillMaxWidth().semantics {
- role = Role.Button
- contentDescription = label
- },
- color = MaterialTheme.colorScheme.primaryContainer,
- shape = RoundedCornerShape(28.dp),
- contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
- borderStroke = BorderStroke(8.dp, MaterialTheme.colorScheme.surface),
- onClick = onClick,
- ) {
- Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+ BottomComponentButtonSurface {
+ Expandable(
+ modifier =
+ Modifier.fillMaxSize().padding(8.dp).semantics {
+ role = Role.Button
+ contentDescription = label
+ },
+ color = MaterialTheme.colorScheme.primaryContainer,
+ shape = RoundedCornerShape(28.dp),
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ onClick = onClick,
+ ) {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+ }
}
}
Text(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 51ec63b16fbb..780e3f2de4c8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -16,23 +16,23 @@
package com.android.systemui.volume.panel.component.button.ui.composable
-import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedIconToggleButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
@@ -62,26 +62,33 @@ class ToggleButtonComponent(
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
- OutlinedIconToggleButton(
- modifier =
- Modifier.height(64.dp).fillMaxWidth().semantics {
- role = Role.Switch
- contentDescription = label
- },
- checked = viewModel.isChecked,
- onCheckedChange = onCheckedChange,
- shape = RoundedCornerShape(28.dp),
- colors =
- IconButtonDefaults.outlinedIconToggleButtonColors(
- containerColor = MaterialTheme.colorScheme.surface,
- contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
- checkedContainerColor = MaterialTheme.colorScheme.primaryContainer,
- checkedContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
- ),
- border = BorderStroke(8.dp, MaterialTheme.colorScheme.surface),
- ) {
- Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+ BottomComponentButtonSurface {
+ val colors =
+ if (viewModel.isChecked) {
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ )
+ } else {
+ ButtonDefaults.buttonColors(
+ containerColor = Color.Transparent,
+ contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ }
+ Button(
+ modifier =
+ Modifier.fillMaxSize().padding(8.dp).semantics {
+ role = Role.Switch
+ contentDescription = label
+ },
+ onClick = { onCheckedChange(!viewModel.isChecked) },
+ shape = RoundedCornerShape(28.dp),
+ colors = colors
+ ) {
+ Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+ }
}
+
Text(
modifier = Modifier.clearAndSetSemantics {}.basicMarquee(),
text = label,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
index 9f9bc623a6b3..b489dfc2e39b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
@@ -59,7 +59,7 @@ constructor(
* @param content is the popup body
*/
fun show(
- expandable: Expandable,
+ expandable: Expandable?,
title: @Composable (SystemUIDialog) -> Unit,
content: @Composable (SystemUIDialog) -> Unit,
) {
@@ -70,7 +70,7 @@ constructor(
) {
PopupComposable(it, title, content)
}
- val controller = expandable.dialogTransitionController()
+ val controller = expandable?.dialogTransitionController()
if (controller == null) {
dialog.show()
} else {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
index e2d7d1165bb7..c74331477229 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
@@ -46,6 +46,12 @@ import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.selected
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
@@ -112,10 +118,16 @@ fun VolumePanelRadioButtonBar(
horizontalArrangement = Arrangement.spacedBy(spacing)
) {
for (itemIndex in items.indices) {
+ val item = items[itemIndex]
Row(
modifier =
Modifier.height(48.dp)
.weight(1f)
+ .semantics {
+ item.contentDescription?.let { contentDescription = it }
+ role = Role.Switch
+ selected = itemIndex == scope.selectedIndex
+ }
.clickable(
interactionSource = null,
indication = null,
@@ -124,7 +136,6 @@ fun VolumePanelRadioButtonBar(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
- val item = items[itemIndex]
if (item.icon !== Empty) {
with(items[itemIndex]) { icon() }
}
@@ -138,7 +149,8 @@ fun VolumePanelRadioButtonBar(
start = indicatorBackgroundPadding,
top = labelIndicatorBackgroundSpacing,
end = indicatorBackgroundPadding
- ),
+ )
+ .clearAndSetSemantics {},
horizontalArrangement = Arrangement.spacedBy(spacing),
) {
for (itemIndex in items.indices) {
@@ -296,6 +308,7 @@ interface VolumePanelRadioButtonBarScope {
onItemSelected: () -> Unit,
icon: @Composable RowScope.() -> Unit = Empty,
label: @Composable RowScope.() -> Unit = Empty,
+ contentDescription: String? = null,
)
}
@@ -317,6 +330,7 @@ private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScop
onItemSelected: () -> Unit,
icon: @Composable RowScope.() -> Unit,
label: @Composable RowScope.() -> Unit,
+ contentDescription: String?,
) {
require(!isSelected || !hasSelectedItem) { "Only one item should be selected at a time" }
if (isSelected) {
@@ -327,6 +341,7 @@ private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScop
onItemSelected = onItemSelected,
icon = icon,
label = label,
+ contentDescription = contentDescription,
)
)
}
@@ -340,6 +355,7 @@ private class Item(
val onItemSelected: () -> Unit,
val icon: @Composable RowScope.() -> Unit,
val label: @Composable RowScope.() -> Unit,
+ val contentDescription: String?,
)
private const val UNSET_OFFSET = -1
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 6673afdfafb4..9a98bdeec8f1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -26,6 +26,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.toColor
@@ -34,6 +35,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
import com.android.systemui.volume.panel.component.selector.ui.composable.VolumePanelRadioButtonBar
import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
class SpatialAudioPopup
@@ -41,10 +43,17 @@ class SpatialAudioPopup
constructor(
private val viewModel: SpatialAudioViewModel,
private val volumePanelPopup: VolumePanelPopup,
+ private val uiEventLogger: UiEventLogger,
) {
/** Shows a popup with the [expandable] animation. */
fun show(expandable: Expandable) {
+ uiEventLogger.logWithPosition(
+ VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN,
+ 0,
+ null,
+ viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isChecked }
+ )
volumePanelPopup.show(expandable, { Title() }, { Content(it) })
}
@@ -74,9 +83,11 @@ constructor(
}
VolumePanelRadioButtonBar {
for (buttonViewModel in enabledModelStates) {
+ val label = buttonViewModel.button.label.toString()
item(
isSelected = buttonViewModel.button.isChecked,
onItemSelected = { viewModel.setEnabled(buttonViewModel.model) },
+ contentDescription = label,
icon = {
Icon(
icon = buttonViewModel.button.icon,
@@ -86,7 +97,7 @@ constructor(
label = {
Text(
modifier = Modifier.basicMarquee(),
- text = buttonViewModel.button.label.toString(),
+ text = label,
style = MaterialTheme.typography.labelMedium,
color = buttonViewModel.labelColor.toColor(),
textAlign = TextAlign.Center,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index f89669c8456c..a54d005c990a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -85,6 +85,7 @@ fun ColumnVolumeSliders(
onValueChange = { newValue: Float ->
sliderViewModel.onValueChanged(sliderState, newValue)
},
+ onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
@@ -131,6 +132,7 @@ fun ColumnVolumeSliders(
onValueChange = { newValue: Float ->
sliderViewModel.onValueChanged(sliderState, newValue)
},
+ onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
index b284c691ef0e..bb17499f021f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
@@ -46,6 +46,7 @@ fun GridVolumeSliders(
onValueChange = { newValue: Float ->
sliderViewModel.onValueChanged(sliderState, newValue)
},
+ onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 19d3f599ef31..228d29259038 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -16,13 +16,15 @@
package com.android.systemui.volume.panel.component.volume.ui.composable
+import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
-import androidx.compose.material3.LocalContentColor
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
@@ -49,6 +51,7 @@ import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.Sl
fun VolumeSlider(
state: SliderState,
onValueChange: (newValue: Float) -> Unit,
+ onValueChangeFinished: (() -> Unit)? = null,
onIconTapped: () -> Unit,
modifier: Modifier = Modifier,
sliderColors: PlatformSliderColors,
@@ -83,28 +86,31 @@ fun VolumeSlider(
value = value,
valueRange = state.valueRange,
onValueChange = onValueChange,
+ onValueChangeFinished = onValueChangeFinished,
enabled = state.isEnabled,
- icon = { isDragging ->
- if (isDragging) {
- Text(text = state.valueText, color = LocalContentColor.current)
- } else {
- state.icon?.let {
- SliderIcon(
- icon = it,
- onIconTapped = onIconTapped,
- isTappable = state.isMutable,
- )
- }
+ icon = {
+ state.icon?.let {
+ SliderIcon(
+ icon = it,
+ onIconTapped = onIconTapped,
+ isTappable = state.isMutable,
+ )
}
},
colors = sliderColors,
- label = {
- VolumeSliderContent(
- modifier = Modifier,
- label = state.label,
- isEnabled = state.isEnabled,
- disabledMessage = state.disabledMessage,
- )
+ label = { isDragging ->
+ AnimatedVisibility(
+ visible = !isDragging,
+ enter = fadeIn(tween(150)),
+ exit = fadeOut(tween(150)),
+ ) {
+ VolumeSliderContent(
+ modifier = Modifier,
+ label = state.label,
+ isEnabled = state.isEnabled,
+ disabledMessage = state.disabledMessage,
+ )
+ }
}
)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index b1cfdcf07977..dbec059715b4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -204,15 +204,16 @@ internal class SceneTransitionLayoutImpl(
}
// Handle back events.
- // TODO(b/290184746): Make sure that this works with SystemUI once we use
- // SceneTransitionLayout in Flexiglass.
- scene(state.transitionState.currentScene).userActions[Back]?.let { result ->
- // TODO(b/290184746): Handle predictive back and use result.distance if
- // specified.
- BackHandler {
- val targetScene = result.toScene
- if (state.canChangeScene(targetScene)) {
- with(state) { coroutineScope.onChangeScene(targetScene) }
+ val targetSceneForBackOrNull =
+ scene(state.transitionState.currentScene).userActions[Back]?.toScene
+ BackHandler(
+ enabled = targetSceneForBackOrNull != null,
+ ) {
+ targetSceneForBackOrNull?.let { targetSceneForBack ->
+ // TODO(b/290184746): Handle predictive back and use result.distance if
+ // specified.
+ if (state.canChangeScene(targetSceneForBack)) {
+ with(state) { coroutineScope.onChangeScene(targetSceneForBack) }
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 1120914fec7c..2d4b63ef2c27 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -66,6 +66,7 @@ import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.scene.shared.model.FakeSceneDataSource
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
@@ -209,7 +210,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
mSetFlagsRule.enableFlags(
@@ -217,7 +217,8 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
)
mSetFlagsRule.disableFlags(
FLAG_SIDEFPS_CONTROLLER_REFACTOR,
- AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+ AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
)
keyguardPasswordViewController =
@@ -267,7 +268,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
falsingManager,
userSwitcherController,
featureFlags,
- kosmos.fakeSceneContainerFlags,
+ kosmos.sceneContainerFlags,
globalSettings,
sessionTracker,
Optional.of(sideFpsController),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 4950b96b077f..85774c67bccb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -537,4 +537,65 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
assertThat(lp.height).isEqualTo(overlayParams.sensorBounds.height())
}
}
+
+ @Test
+ fun addViewPending_layoutIsNotUpdated() =
+ testScope.runTest {
+ withReasonSuspend(REASON_AUTH_KEYGUARD) {
+ mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
+ mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+
+ // GIVEN going to sleep
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OFF,
+ to = KeyguardState.GONE,
+ testScope = this,
+ )
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_SLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ runCurrent()
+
+ // WHEN a request comes to show the view
+ controllerOverlay.show(udfpsController, overlayParams)
+ runCurrent()
+
+ // THEN the view does not get added immediately
+ verify(windowManager, never()).addView(any(), any())
+
+ // WHEN updateOverlayParams gets called when the view is pending to be added
+ controllerOverlay.updateOverlayParams(overlayParams)
+
+ // THEN the view layout is never updated
+ verify(windowManager, never()).updateViewLayout(any(), any())
+
+ // CLEANUPL we hide to end the job that listens for the finishedGoingToSleep signal
+ controllerOverlay.hide()
+ }
+ }
+
+ @Test
+ fun updateOverlayParams_viewLayoutUpdated() =
+ testScope.runTest {
+ withReasonSuspend(REASON_AUTH_KEYGUARD) {
+ mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ runCurrent()
+ controllerOverlay.show(udfpsController, overlayParams)
+ runCurrent()
+ verify(windowManager).addView(any(), any())
+
+ // WHEN updateOverlayParams gets called
+ controllerOverlay.updateOverlayParams(overlayParams)
+
+ // THEN the view layout is updated
+ verify(windowManager, never()).updateViewLayout(any(), any())
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 3afca96e07a0..0db0e0767767 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -18,6 +18,10 @@ package com.android.systemui.bouncer.ui.viewmodel
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -34,7 +38,10 @@ import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
+import com.android.systemui.truth.containsEntriesExactly
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -193,6 +200,23 @@ class BouncerViewModelTest : SysuiTestCase() {
assertThat(isFoldSplitRequired).isTrue()
}
+ @Test
+ fun destinationScenes() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ kosmos.fakeSceneDataSource.changeScene(Scenes.QuickSettings)
+ runCurrent()
+
+ kosmos.fakeSceneDataSource.changeScene(Scenes.Bouncer)
+ runCurrent()
+
+ assertThat(destinationScenes)
+ .containsEntriesExactly(
+ Back to UserActionResult(Scenes.QuickSettings),
+ Swipe(SwipeDirection.Down) to UserActionResult(Scenes.QuickSettings),
+ )
+ }
+
private fun authMethodsToTest(): List<AuthenticationMethodModel> {
return listOf(None, Pin, Password, Pattern, Sim)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index a944afb70f38..76f15d2ed257 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -120,19 +120,20 @@ class CommunalSceneStartableTest : SysuiTestCase() {
}
@Test
- fun exitingDream_forceCommunalScene() =
+ fun occluded_forceBlankScene() =
with(kosmos) {
testScope.runTest {
val scene by collectLastValue(communalInteractor.desiredScene)
- assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ communalInteractor.changeScene(CommunalScenes.Communal)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
updateDocked(true)
fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.DREAMING,
- to = KeyguardState.LOCKSCREEN,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
testScope = this
)
- assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index f71121c43ff8..ce7b60e86f8f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -23,6 +23,7 @@ import android.app.admin.devicePolicyManager
import android.appwidget.AppWidgetProviderInfo
import android.content.Intent
import android.content.pm.UserInfo
+import android.os.UserManager.USER_TYPE_PROFILE_MANAGED
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
@@ -59,6 +60,7 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
setKeyguardFeaturesDisabled(SECONDARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
+ setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_FEATURES_NONE)
underTest = kosmos.communalSettingsRepository
}
@@ -133,6 +135,30 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
@EnableFlags(FLAG_COMMUNAL_HUB)
@Test
+ fun widgetsAllowedForWorkProfile_isFalse_whenDisallowedByDevicePolicy() =
+ testScope.runTest {
+ val widgetsAllowedForWorkProfile by
+ collectLastValue(underTest.getAllowedByDevicePolicy(WORK_PROFILE))
+ assertThat(widgetsAllowedForWorkProfile).isTrue()
+
+ setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_WIDGETS_ALL)
+ assertThat(widgetsAllowedForWorkProfile).isFalse()
+ }
+
+ @EnableFlags(FLAG_COMMUNAL_HUB)
+ @Test
+ fun hubIsEnabled_whenDisallowedByDevicePolicyForWorkProfile() =
+ testScope.runTest {
+ val enabledStateForPrimaryUser by
+ collectLastValue(underTest.getEnabledState(PRIMARY_USER))
+ assertThat(enabledStateForPrimaryUser?.enabled).isTrue()
+
+ setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_WIDGETS_ALL)
+ assertThat(enabledStateForPrimaryUser?.enabled).isTrue()
+ }
+
+ @EnableFlags(FLAG_COMMUNAL_HUB)
+ @Test
fun hubIsDisabledByUserAndDevicePolicy() =
testScope.runTest {
val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
@@ -189,5 +215,13 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
val PRIMARY_USER =
UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
val SECONDARY_USER = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
+ val WORK_PROFILE =
+ UserInfo(
+ 10,
+ "work",
+ /* iconPath= */ "",
+ /* flags= */ 0,
+ USER_TYPE_PROFILE_MANAGED,
+ )
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index e7ccde26e161..f21e9697c91f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -17,6 +17,8 @@
package com.android.systemui.communal.domain.interactor
+import android.app.admin.DevicePolicyManager
+import android.app.admin.devicePolicyManager
import android.app.smartspace.SmartspaceTarget
import android.appwidget.AppWidgetProviderInfo
import android.content.Intent
@@ -32,6 +34,7 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
@@ -71,6 +74,7 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
@@ -929,7 +933,6 @@ class CommunalInteractorTest : SysuiTestCase() {
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- userRepository.setSelectedUserInfo(mainUser)
val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
userRepository.setUserInfos(userInfos)
@@ -937,6 +940,7 @@ class CommunalInteractorTest : SysuiTestCase() {
userInfos = userInfos,
selectedUserIndex = 0,
)
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
runCurrent()
// Widgets available.
@@ -955,7 +959,6 @@ class CommunalInteractorTest : SysuiTestCase() {
AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
mainUser.id
)
- runCurrent()
// Only the keyguard widget is enabled.
assertThat(widgetContent).hasSize(3)
@@ -974,7 +977,6 @@ class CommunalInteractorTest : SysuiTestCase() {
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- userRepository.setSelectedUserInfo(mainUser)
val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
userRepository.setUserInfos(userInfos)
@@ -982,6 +984,7 @@ class CommunalInteractorTest : SysuiTestCase() {
userInfos = userInfos,
selectedUserIndex = 0,
)
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
runCurrent()
// Widgets available.
@@ -1001,7 +1004,6 @@ class CommunalInteractorTest : SysuiTestCase() {
AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
mainUser.id
)
- runCurrent()
// All widgets are enabled.
assertThat(widgetContent).hasSize(3)
@@ -1011,6 +1013,79 @@ class CommunalInteractorTest : SysuiTestCase() {
}
}
+ @Test
+ fun filterWidgets_whenDisallowedByDevicePolicyForWorkProfile() =
+ testScope.runTest {
+ // Keyguard showing, and tutorial completed.
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+ val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ runCurrent()
+
+ val widgetContent by collectLastValue(underTest.widgetContent)
+ // Given three widgets, and one of them is associated with work profile.
+ val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
+ val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+ val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
+ val widgets = listOf(widget1, widget2, widget3)
+ widgetRepository.setCommunalWidgets(widgets)
+
+ setKeyguardFeaturesDisabled(
+ USER_INFO_WORK,
+ DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
+ )
+
+ // Widget under work profile is filtered out and the remaining two link to main user id.
+ assertThat(widgetContent).hasSize(2)
+ widgetContent!!.forEach { model ->
+ assertThat(model.providerInfo.profile?.identifier).isEqualTo(MAIN_USER_INFO.id)
+ }
+ }
+
+ @Test
+ fun filterWidgets_whenAllowedByDevicePolicyForWorkProfile() =
+ testScope.runTest {
+ // Keyguard showing, and tutorial completed.
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+ val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ runCurrent()
+
+ val widgetContent by collectLastValue(underTest.widgetContent)
+ // Given three widgets, and one of them is associated with work profile.
+ val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
+ val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+ val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
+ val widgets = listOf(widget1, widget2, widget3)
+ widgetRepository.setCommunalWidgets(widgets)
+
+ setKeyguardFeaturesDisabled(
+ USER_INFO_WORK,
+ DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
+ )
+
+ // Widget under work profile is available.
+ assertThat(widgetContent).hasSize(3)
+ assertThat(widgetContent!![0].providerInfo.profile?.identifier)
+ .isEqualTo(USER_INFO_WORK.id)
+ }
+
private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
val timer = mock(SmartspaceTarget::class.java)
whenever(timer.smartspaceTargetId).thenReturn(id)
@@ -1020,6 +1095,15 @@ class CommunalInteractorTest : SysuiTestCase() {
return timer
}
+ private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
+ whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
+ .thenReturn(disabledFlags)
+ kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ )
+ }
+
private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
mock<CommunalWidgetContentModel> {
whenever(this.appWidgetId).thenReturn(appWidgetId)
@@ -1044,6 +1128,13 @@ class CommunalInteractorTest : SysuiTestCase() {
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
- val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE)
+ val USER_INFO_WORK =
+ UserInfo(
+ 10,
+ "work",
+ /* iconPath= */ "",
+ /* flags= */ 0,
+ UserManager.USER_TYPE_PROFILE_MANAGED,
+ )
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 36919d0c74a4..6b2a1d59e62d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -32,7 +32,6 @@ import android.hardware.face.FaceManager
import android.hardware.face.FaceSensorProperties
import android.hardware.face.FaceSensorPropertiesInternal
import android.os.CancellationSignal
-import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId.fakeInstanceId
@@ -54,7 +53,6 @@ import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
-import com.android.systemui.display.data.repository.display
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
@@ -68,6 +66,7 @@ import com.android.systemui.keyguard.data.repository.fakeTrustRepository
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.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testDispatcher
@@ -697,9 +696,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
)
runCurrent()
- displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_OFF)))
- displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
-
+ displayRepository.setDefaultDisplayOff(true)
runCurrent()
assertThat(canFaceAuthRun()).isTrue()
@@ -717,10 +714,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
)
runCurrent()
- displayRepository.emit(
- setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_OFF))
- )
- displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+ displayRepository.setDefaultDisplayOff(true)
}
}
@@ -827,21 +821,37 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
- fun isAuthenticatedIsResetToFalseWhenKeyguardDoneAnimationsFinished() =
+ fun isAuthenticatedIsResetToFalseWhenFinishedTransitioningToGoneAndStatusBarStateShade() =
testScope.runTest {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
triggerFaceAuth(false)
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
authenticationCallback.value.onAuthenticationSucceeded(
mock(FaceManager.AuthenticationResult::class.java)
)
assertThat(authenticated()).isTrue()
- keyguardRepository.keyguardDoneAnimationsFinished()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ assertThat(authenticated()).isTrue()
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
assertThat(authenticated()).isFalse()
}
@@ -1161,8 +1171,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
faceLockoutResetCallback.value.onLockoutReset(0)
bouncerRepository.setAlternateVisible(true)
keyguardRepository.setKeyguardShowing(true)
- displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_ON)))
- displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+ displayRepository.setDefaultDisplayOff(false)
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
new file mode 100644
index 000000000000..70582daf8e9f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardClockSwitch.LARGE
+import com.android.keyguard.KeyguardClockSwitch.SMALL
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardClockRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardClockInteractorTest : SysuiTestCase() {
+ private lateinit var kosmos: Kosmos
+ private lateinit var underTest: KeyguardClockInteractor
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setup() {
+ kosmos = testKosmos()
+ testScope = kosmos.testScope
+ underTest = kosmos.keyguardClockInteractor
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun clockSize_sceneContainerFlagOff_basedOnRepository() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockSize)
+ kosmos.keyguardClockRepository.setClockSize(LARGE)
+ assertThat(value).isEqualTo(LARGE)
+
+ kosmos.keyguardClockRepository.setClockSize(SMALL)
+ assertThat(value).isEqualTo(SMALL)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOff_basedOnRepository() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.keyguardInteractor.setClockShouldBeCentered(true)
+ assertThat(value).isEqualTo(true)
+
+ kosmos.keyguardInteractor.setClockShouldBeCentered(false)
+ assertThat(value).isEqualTo(false)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun clockSize_forceSmallClock_SMALL() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockSize)
+ kosmos.fakeKeyguardClockRepository.setShouldForceSmallClock(true)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, true)
+ transitionTo(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
+ assertThat(value).isEqualTo(SMALL)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun clockSize_SceneContainerFlagOn_shadeModeSingle_hasNotifs_SMALL() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockSize)
+ kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
+ kosmos.activeNotificationListRepository.setActiveNotifs(1)
+ assertThat(value).isEqualTo(SMALL)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun clockSize_SceneContainerFlagOn_shadeModeSingle_hasMedia_SMALL() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockSize)
+ kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
+ val userMedia = MediaData().copy(active = true)
+ kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ assertThat(value).isEqualTo(SMALL)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun clockSize_SceneContainerFlagOn_shadeModeSplit_isMediaVisible_SMALL() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockSize)
+ val userMedia = MediaData().copy(active = true)
+ kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ kosmos.keyguardRepository.setIsDozing(false)
+ assertThat(value).isEqualTo(SMALL)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun clockSize_SceneContainerFlagOn_shadeModeSplit_noMedia_LARGE() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockSize)
+ kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.keyguardRepository.setIsDozing(false)
+ assertThat(value).isEqualTo(LARGE)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun clockSize_SceneContainerFlagOn_shadeModeSplit_isDozing_LARGE() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockSize)
+ val userMedia = MediaData().copy(active = true)
+ kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ kosmos.keyguardRepository.setIsDozing(true)
+ assertThat(value).isEqualTo(LARGE)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOn_notSplitMode_true() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
+ assertThat(value).isEqualTo(true)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_noActiveNotifications_true() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.activeNotificationListRepository.setActiveNotifs(0)
+ assertThat(value).isEqualTo(true)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_isActiveDreamLockscreenHosted_true() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.activeNotificationListRepository.setActiveNotifs(1)
+ kosmos.keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ assertThat(value).isEqualTo(true)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_hasPulsingNotifications_false() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.activeNotificationListRepository.setActiveNotifs(1)
+ kosmos.headsUpNotificationRepository.headsUpAnimatingAway.value = true
+ kosmos.keyguardRepository.setIsDozing(true)
+ assertThat(value).isEqualTo(false)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_onAod_true() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.activeNotificationListRepository.setActiveNotifs(1)
+ transitionTo(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+ assertThat(value).isEqualTo(true)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_offAod_false() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.activeNotificationListRepository.setActiveNotifs(1)
+ transitionTo(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
+ assertThat(value).isEqualTo(false)
+ }
+
+ private suspend fun transitionTo(from: KeyguardState, to: KeyguardState) {
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from, to, 0f, TransitionState.STARTED)
+ )
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from, to, 0.5f, TransitionState.RUNNING)
+ )
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from, to, 1f, TransitionState.FINISHED)
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 6d7a0a96a71b..1dd5d073bef3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -38,6 +38,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -75,7 +76,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
repository = repository,
commandQueue = commandQueue,
powerInteractor = PowerInteractorFactory.create().powerInteractor,
- sceneContainerFlags = kosmos.fakeSceneContainerFlags,
+ sceneContainerFlags = kosmos.sceneContainerFlags,
bouncerRepository = bouncerRepository,
configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
shadeRepository = shadeRepository,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
index 78bdfb350085..5bf0f4b8dc28 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
@@ -36,6 +36,7 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,16 +46,19 @@ import org.junit.runner.RunWith
class AlternateBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
val kosmos =
testKosmos().apply {
- fakeFeatureFlagsClassic.apply {
- set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
- set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- }
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
+
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
private val underTest by lazy { kosmos.alternateBouncerToGoneTransitionViewModel }
+ @Before
+ fun setup() {
+ mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT)
+ }
+
@Test
fun deviceEntryParentViewDisappear() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
index ff41ea25f470..854a4786d059 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
@@ -32,6 +32,7 @@ import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,15 +42,17 @@ import org.junit.runner.RunWith
class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
- fakeFeatureFlagsClassic.apply {
- set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
- set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- }
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
private val testScope = kosmos.testScope
private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
private val underTest by lazy { kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel }
+ @Before
+ fun setup() {
+ mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT)
+ }
+
@Test
fun deviceEntryParentViewDisappear() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
index e6b30176d3ee..ee217a63e8ac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -57,10 +57,7 @@ class BouncerToGoneFlowsTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
- fakeFeatureFlagsClassic.apply {
- set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
- set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- }
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
@@ -72,6 +69,7 @@ class BouncerToGoneFlowsTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(false)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index fc604aa5a1d2..e3eca6729188 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -30,11 +30,8 @@ import com.android.systemui.communal.data.repository.communalRepository
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -60,13 +57,9 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardRootViewModelTest : SysuiTestCase() {
- private val kosmos =
- testKosmos().apply {
- fakeFeatureFlagsClassic.apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) }
- }
+ private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val keyguardInteractor = kosmos.keyguardInteractor
private val keyguardRepository = kosmos.fakeKeyguardRepository
private val communalRepository = kosmos.communalRepository
private val screenOffAnimationController = kosmos.screenOffAnimationController
@@ -82,7 +75,10 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
fun setUp() {
mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION)
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ mSetFlagsRule.disableFlags(
+ AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
+ AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
+ )
}
@Test
@@ -426,4 +422,23 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
shadeRepository.setQsExpansion(0.5f)
assertThat(alpha).isEqualTo(0f)
}
+
+ @Test
+ fun alpha_idleOnDream_isZero() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha(viewState))
+ assertThat(alpha).isEqualTo(1f)
+
+ // Go to GONE state
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope = testScope,
+ )
+ assertThat(alpha).isEqualTo(0f)
+
+ // Try pulling down shade and ensure the value doesn't change
+ shadeRepository.setQsExpansion(0.5f)
+ assertThat(alpha).isEqualTo(0f)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 66f7e015a133..776f1a55fdcb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -34,6 +34,8 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.shadeRepository
@@ -43,6 +45,7 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificati
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import kotlin.math.pow
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.BeforeClass
@@ -57,22 +60,26 @@ import platform.test.runner.parameterized.Parameters
class LockscreenSceneViewModelTest : SysuiTestCase() {
companion object {
+ private const val parameterCount = 6
+
@Parameters(
name =
"canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," +
- " isSingleShade={3}, isCommunalAvailable={4}"
+ " isSingleShade={3}, isCommunalAvailable={4}, isShadeTouchable={5}"
)
@JvmStatic
fun combinations() = buildList {
- repeat(32) { combination ->
+ repeat(2f.pow(parameterCount).toInt()) { combination ->
add(
arrayOf(
- /* canSwipeToEnter= */ combination and 1 != 0,
- /* downWithTwoPointers= */ combination and 2 != 0,
- /* downFromEdge= */ combination and 4 != 0,
- /* isSingleShade= */ combination and 8 != 0,
- /* isCommunalAvailable= */ combination and 16 != 0,
- )
+ /* canSwipeToEnter= */ combination and 1 != 0,
+ /* downWithTwoPointers= */ combination and 2 != 0,
+ /* downFromEdge= */ combination and 4 != 0,
+ /* isSingleShade= */ combination and 8 != 0,
+ /* isCommunalAvailable= */ combination and 16 != 0,
+ /* isShadeTouchable= */ combination and 32 != 0,
+ )
+ .also { check(it.size == parameterCount) }
)
}
}
@@ -82,8 +89,15 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
fun setUp() {
val combinationStrings =
combinations().map { array ->
- check(array.size == 5)
- "${array[4]},${array[3]},${array[2]},${array[1]},${array[0]}"
+ check(array.size == parameterCount)
+ buildString {
+ ((parameterCount - 1) downTo 0).forEach { index ->
+ append("${array[index]}")
+ if (index > 0) {
+ append(",")
+ }
+ }
+ }
}
val uniqueCombinations = combinationStrings.toSet()
assertThat(combinationStrings).hasSize(uniqueCombinations.size)
@@ -92,8 +106,35 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
private fun expectedDownDestination(
downFromEdge: Boolean,
isSingleShade: Boolean,
- ): SceneKey {
- return if (downFromEdge && isSingleShade) Scenes.QuickSettings else Scenes.Shade
+ isShadeTouchable: Boolean,
+ ): SceneKey? {
+ return when {
+ !isShadeTouchable -> null
+ downFromEdge && isSingleShade -> Scenes.QuickSettings
+ else -> Scenes.Shade
+ }
+ }
+
+ private fun expectedUpDestination(
+ canSwipeToEnter: Boolean,
+ isShadeTouchable: Boolean,
+ ): SceneKey? {
+ return when {
+ !isShadeTouchable -> null
+ canSwipeToEnter -> Scenes.Gone
+ else -> Scenes.Bouncer
+ }
+ }
+
+ private fun expectedLeftDestination(
+ isCommunalAvailable: Boolean,
+ isShadeTouchable: Boolean,
+ ): SceneKey? {
+ return when {
+ !isShadeTouchable -> null
+ isCommunalAvailable -> Scenes.Communal
+ else -> null
+ }
}
}
@@ -106,6 +147,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
@JvmField @Parameter(2) var downFromEdge: Boolean = false
@JvmField @Parameter(3) var isSingleShade: Boolean = true
@JvmField @Parameter(4) var isCommunalAvailable: Boolean = false
+ @JvmField @Parameter(5) var isShadeTouchable: Boolean = false
private val underTest by lazy { createLockscreenSceneViewModel() }
@@ -130,6 +172,14 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
}
)
kosmos.setCommunalAvailable(isCommunalAvailable)
+ kosmos.fakePowerRepository.updateWakefulness(
+ rawState =
+ if (isShadeTouchable) {
+ WakefulnessState.AWAKE
+ } else {
+ WakefulnessState.ASLEEP
+ },
+ )
val destinationScenes by collectLastValue(underTest.destinationScenes)
@@ -148,14 +198,25 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
expectedDownDestination(
downFromEdge = downFromEdge,
isSingleShade = isSingleShade,
+ isShadeTouchable = isShadeTouchable,
)
)
assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
- .isEqualTo(if (canSwipeToEnter) Scenes.Gone else Scenes.Bouncer)
+ .isEqualTo(
+ expectedUpDestination(
+ canSwipeToEnter = canSwipeToEnter,
+ isShadeTouchable = isShadeTouchable,
+ )
+ )
assertThat(destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene)
- .isEqualTo(Scenes.Communal.takeIf { isCommunalAvailable })
+ .isEqualTo(
+ expectedLeftDestination(
+ isCommunalAvailable = isCommunalAvailable,
+ isShadeTouchable = isShadeTouchable,
+ )
+ )
}
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index db1d5d91eb65..18e9009611c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -44,10 +44,7 @@ import org.junit.runner.RunWith
class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
val kosmos =
testKosmos().apply {
- fakeFeatureFlagsClassic.apply {
- set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
- set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- }
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
val testScope = kosmos.testScope
@@ -58,6 +55,7 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
@Before
fun setUp() {
+ mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(false)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
index 956ef661d467..33eb90acdcb3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
@@ -26,7 +26,9 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.MediaTestHelper
import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -144,6 +146,37 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
assertThat(smartspaceMediaData?.isActive).isFalse()
}
+ @Test
+ fun addMediaDataLoadingState() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(underTest.mediaDataLoadedStates)
+ val instanceId = InstanceId.fakeInstanceId(123)
+ val mediaLoadedStates = mutableListOf(MediaDataLoadingModel.Loaded(instanceId))
+
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates)
+
+ mediaLoadedStates.remove(MediaDataLoadingModel.Loaded(instanceId))
+
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Removed(instanceId))
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates)
+ }
+
+ @Test
+ fun setRecommendationsLoadingState() =
+ testScope.runTest {
+ val recommendationsLoadingState by
+ collectLastValue(underTest.recommendationsLoadingState)
+ val recommendationsLoadingModel =
+ SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE)
+
+ underTest.setRecommedationsLoadingState(recommendationsLoadingModel)
+
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
+ }
+
companion object {
private const val KEY = "KEY"
private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
index d9d84f2d2aac..a0a1eb3a6ca6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
@@ -20,6 +20,7 @@ import android.R
import android.graphics.drawable.Icon
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
@@ -28,13 +29,20 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.MediaTestHelper
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
+import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,9 +53,17 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
+ private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
private val mediaFilterRepository: MediaFilterRepository = kosmos.mediaFilterRepository
+
private val underTest: MediaCarouselInteractor = kosmos.mediaCarouselInteractor
+ @Before
+ fun setUp() {
+ underTest.start()
+ }
+
@Test
fun addUserMediaEntry_activeThenInactivate() =
testScope.runTest {
@@ -56,7 +72,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
val hasActiveMedia by collectLastValue(underTest.hasActiveMedia)
val hasAnyMedia by collectLastValue(underTest.hasAnyMedia)
- val userMedia = MediaData().copy(active = true)
+ val userMedia = MediaData(active = true)
mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
@@ -79,7 +95,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
val hasActiveMedia by collectLastValue(underTest.hasActiveMedia)
val hasAnyMedia by collectLastValue(underTest.hasAnyMedia)
- val userMedia = MediaData().copy(active = false)
+ val userMedia = MediaData(active = false)
val instanceId = userMedia.instanceId
mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
@@ -112,7 +128,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
isActive = true,
recommendations = MediaTestHelper.getValidRecommendationList(icon),
)
- val userMedia = MediaData().copy(active = false)
+ val userMedia = MediaData(active = false)
mediaFilterRepository.setRecommendation(userMediaRecommendation)
@@ -199,7 +215,80 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
fun hasActiveMediaOrRecommendation_nothingSet_returnsFalse() =
testScope.runTest { assertThat(underTest.hasActiveMediaOrRecommendation.value).isFalse() }
+ @Test
+ fun onMediaDataUpdated_updatesLoadingState() =
+ testScope.runTest {
+ whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+ val mediaDataLoadedStates by collectLastValue(underTest.mediaDataLoadedStates)
+ val instanceId = InstanceId.fakeInstanceId(123)
+ val mediaLoadedStates: MutableList<MediaDataLoadingModel> = mutableListOf()
+
+ mediaLoadedStates.add(MediaDataLoadingModel.Loaded(instanceId))
+ mediaDataFilter.onMediaDataLoaded(
+ KEY,
+ KEY,
+ MediaData(userId = USER_ID, instanceId = instanceId)
+ )
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates)
+
+ val newInstanceId = InstanceId.fakeInstanceId(321)
+
+ mediaLoadedStates.add(MediaDataLoadingModel.Loaded(newInstanceId))
+ mediaDataFilter.onMediaDataLoaded(
+ KEY_2,
+ KEY_2,
+ MediaData(userId = USER_ID, instanceId = newInstanceId)
+ )
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates)
+
+ mediaLoadedStates.remove(MediaDataLoadingModel.Loaded(instanceId))
+
+ mediaDataFilter.onMediaDataRemoved(KEY)
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates)
+
+ mediaLoadedStates.remove(MediaDataLoadingModel.Loaded(newInstanceId))
+
+ mediaDataFilter.onMediaDataRemoved(KEY_2)
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates)
+ }
+
+ @Test
+ fun onMediaRecommendationsUpdated_updatesLoadingState() =
+ testScope.runTest {
+ whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+ val recommendationsLoadingState by
+ collectLastValue(underTest.recommendationsLoadingState)
+ val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
+ val mediaRecommendations =
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = true,
+ recommendations = MediaTestHelper.getValidRecommendationList(icon),
+ )
+ var recommendationsLoadingModel: SmartspaceMediaLoadingModel =
+ SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, isPrioritized = true)
+
+ mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, mediaRecommendations)
+
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
+
+ recommendationsLoadingModel = SmartspaceMediaLoadingModel.Removed(KEY_MEDIA_SMARTSPACE)
+
+ mediaDataFilter.onSmartspaceMediaDataRemoved(KEY_MEDIA_SMARTSPACE)
+
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
+ }
+
companion object {
+ private const val KEY = "key"
+ private const val KEY_2 = "key2"
+ private const val USER_ID = 0
private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index 1a9ad3c285e6..f2eb7f44600e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -35,6 +35,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.QSImpl
import com.android.systemui.qs.dagger.QSComponent
import com.android.systemui.qs.dagger.QSSceneComponent
+import com.android.systemui.settings.brightness.MirrorController
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
@@ -508,4 +509,21 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
underTest.requestCloseCustomizer()
verify(qsImpl!!).closeCustomizer()
}
+
+ @Test
+ fun setBrightnessMirrorController() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+
+ val mirrorController = mock<MirrorController>()
+ underTest.setBrightnessMirrorController(mirrorController)
+
+ verify(qsImpl!!).setBrightnessMirrorController(mirrorController)
+
+ underTest.setBrightnessMirrorController(null)
+ verify(qsImpl!!).setBrightnessMirrorController(null)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index d9ab3b172d2a..139289ae4299 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -31,7 +31,9 @@ import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -87,6 +89,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
flags,
scope = testScope.backgroundScope,
)
+ private val sceneInteractor = kosmos.sceneInteractor
private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
@@ -108,16 +111,18 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
underTest =
QuickSettingsSceneViewModel(
+ brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
footerActionsViewModelFactory = footerActionsViewModelFactory,
footerActionsController = footerActionsController,
+ sceneInteractor = sceneInteractor,
)
}
@Test
- fun destinationsNotCustomizing() =
+ fun destinations_whenNotCustomizing() =
testScope.runTest {
overrideResource(R.bool.config_use_split_notification_shade, false)
val destinations by collectLastValue(underTest.destinationScenes)
@@ -133,7 +138,30 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
}
@Test
- fun destinationsCustomizing_noDestinations() =
+ fun destinations_whenNotCustomizing_withPreviousSceneLockscreen() =
+ testScope.runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ qsFlexiglassAdapter.setCustomizing(false)
+ val destinations by collectLastValue(underTest.destinationScenes)
+
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val previousScene by collectLastValue(sceneInteractor.previousScene)
+ sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+ assertThat(previousScene).isEqualTo(Scenes.Lockscreen)
+
+ assertThat(destinations)
+ .isEqualTo(
+ mapOf(
+ Back to UserActionResult(Scenes.Lockscreen),
+ Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Lockscreen),
+ )
+ )
+ }
+
+ @Test
+ fun destinations_whenCustomizing_noDestinations() =
testScope.runTest {
overrideResource(R.bool.config_use_split_notification_shade, false)
val destinations by collectLastValue(underTest.destinationScenes)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 98cbda2efd2b..93302e32b607 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -74,6 +74,7 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -259,6 +260,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
+ brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
mediaDataManager = mediaDataManager,
shadeInteractor = kosmos.shadeInteractor,
footerActionsController = kosmos.footerActionsController,
@@ -291,6 +293,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
)
startable.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 3d6619272dbe..7f7c24e6efa4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -39,7 +39,6 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
class SceneContainerRepositoryTest : SysuiTestCase() {
private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
@@ -140,4 +139,23 @@ class SceneContainerRepositoryTest : SysuiTestCase() {
ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
)
}
+
+ @Test
+ fun previousScene() =
+ testScope.runTest {
+ val underTest = kosmos.sceneContainerRepository
+ val currentScene by collectLastValue(underTest.currentScene)
+ val previousScene by collectLastValue(underTest.previousScene)
+
+ assertThat(previousScene).isNull()
+
+ val firstScene = currentScene
+ underTest.changeScene(Scenes.Shade)
+ assertThat(previousScene).isEqualTo(firstScene)
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+
+ underTest.changeScene(Scenes.QuickSettings)
+ assertThat(previousScene).isEqualTo(Scenes.Shade)
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 143c0f277209..b179c30e60b9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -291,4 +291,19 @@ class SceneInteractorTest : SysuiTestCase() {
assertThat(isVisible).isFalse()
}
+
+ @Test
+ fun previousScene() =
+ testScope.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ val previousScene by collectLastValue(underTest.previousScene)
+ assertThat(previousScene).isNull()
+
+ val firstScene = currentScene
+ underTest.changeScene(toScene = Scenes.Shade, "reason")
+ assertThat(previousScene).isEqualTo(firstScene)
+
+ underTest.changeScene(toScene = Scenes.QuickSettings, "reason")
+ assertThat(previousScene).isEqualTo(Scenes.Shade)
+ }
}
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 3fd5306abb71..61adcd2e2c25 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
@@ -20,13 +20,11 @@ package com.android.systemui.scene.domain.startable
import android.app.StatusBarManager
import android.os.PowerManager
-import android.platform.test.annotations.EnableFlags
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
@@ -40,6 +38,7 @@ import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepositor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -48,14 +47,17 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
+import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
@@ -91,7 +93,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
-@EnableFlags(AconfigFlags.FLAG_SCENE_CONTAINER)
+@EnableSceneContainer
class SceneContainerStartableTest : SysuiTestCase() {
@Mock private lateinit var windowController: NotificationShadeWindowController
@@ -140,6 +142,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
)
}
@@ -287,6 +290,38 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
@Test
+ fun switchFromBouncerToQuickSettingsWhenDeviceUnlocked() =
+ testScope.runTest {
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+
+ val transitionState =
+ prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = false,
+ initialSceneKey = Scenes.Lockscreen,
+ )
+ assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+ underTest.start()
+ runCurrent()
+
+ sceneInteractor.changeScene(Scenes.QuickSettings, "switching to qs for test")
+ transitionState.value = ObservableTransitionState.Idle(Scenes.QuickSettings)
+ runCurrent()
+ assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
+
+ sceneInteractor.changeScene(Scenes.Bouncer, "switching to bouncer for test")
+ transitionState.value = ObservableTransitionState.Idle(Scenes.Bouncer)
+ runCurrent()
+ assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+
+ assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
+ }
+
+ @Test
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -1127,6 +1162,33 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning.value).isTrue()
}
+ @Test
+ fun switchToLockscreen_whenShadeBecomesNotTouchable() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val isShadeTouchable by collectLastValue(kosmos.shadeInteractor.isShadeTouchable)
+ val transitionStateFlow = prepareState()
+ underTest.start()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ // Flung to bouncer, 90% of the way there:
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.Lockscreen,
+ toScene = Scenes.Bouncer,
+ progress = flowOf(0.9f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ )
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+
+ kosmos.fakePowerRepository.updateWakefulness(WakefulnessState.ASLEEP)
+ runCurrent()
+ assertThat(isShadeTouchable).isFalse()
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ }
+
private fun TestScope.emulateSceneTransition(
transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
toScene: SceneKey,
@@ -1166,6 +1228,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
isLockscreenEnabled: Boolean = true,
startsAwake: Boolean = true,
isDeviceProvisioned: Boolean = true,
+ isInteractive: Boolean = true,
): MutableStateFlow<ObservableTransitionState> {
if (authenticationMethod?.isSecure == true) {
assert(isLockscreenEnabled) {
@@ -1205,6 +1268,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
} else {
powerInteractor.setAsleepForTest()
}
+ kosmos.fakePowerRepository.setInteractive(isInteractive)
kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(isDeviceProvisioned)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt
new file mode 100644
index 000000000000..db31ad52e3b0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.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.scene.shared.flag
+
+import android.platform.test.flag.junit.FlagsParameterization
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN
+import com.android.systemui.Flags.FLAG_EXAMPLE_FLAG
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.andSceneContainer
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class SceneContainerFlagParameterizationTest : SysuiTestCase() {
+
+ @Test
+ fun emptyAndSceneContainer() {
+ val result = FlagsParameterization.allCombinationsOf().andSceneContainer()
+ Truth.assertThat(result).hasSize(2)
+ Truth.assertThat(result[0].mOverrides[FLAG_SCENE_CONTAINER]).isFalse()
+ Truth.assertThat(result[1].mOverrides[FLAG_SCENE_CONTAINER]).isTrue()
+ }
+
+ @Test
+ fun oneUnrelatedAndSceneContainer() {
+ val unrelatedFlag = FLAG_EXAMPLE_FLAG
+ val result = FlagsParameterization.allCombinationsOf(unrelatedFlag).andSceneContainer()
+ Truth.assertThat(result).hasSize(4)
+ Truth.assertThat(result[0].mOverrides[unrelatedFlag]).isFalse()
+ Truth.assertThat(result[0].mOverrides[FLAG_SCENE_CONTAINER]).isFalse()
+ Truth.assertThat(result[1].mOverrides[unrelatedFlag]).isFalse()
+ Truth.assertThat(result[1].mOverrides[FLAG_SCENE_CONTAINER]).isTrue()
+ Truth.assertThat(result[2].mOverrides[unrelatedFlag]).isTrue()
+ Truth.assertThat(result[2].mOverrides[FLAG_SCENE_CONTAINER]).isFalse()
+ Truth.assertThat(result[3].mOverrides[unrelatedFlag]).isTrue()
+ Truth.assertThat(result[3].mOverrides[FLAG_SCENE_CONTAINER]).isTrue()
+ }
+
+ @Test
+ fun oneDependencyAndSceneContainer() {
+ val dependentFlag = FLAG_COMPOSE_LOCKSCREEN
+ val result = FlagsParameterization.allCombinationsOf(dependentFlag).andSceneContainer()
+ Truth.assertThat(result).hasSize(3)
+ Truth.assertThat(result[0].mOverrides[dependentFlag]).isFalse()
+ Truth.assertThat(result[0].mOverrides[FLAG_SCENE_CONTAINER]).isFalse()
+ Truth.assertThat(result[1].mOverrides[dependentFlag]).isTrue()
+ Truth.assertThat(result[1].mOverrides[FLAG_SCENE_CONTAINER]).isFalse()
+ Truth.assertThat(result[2].mOverrides[dependentFlag]).isTrue()
+ Truth.assertThat(result[2].mOverrides[FLAG_SCENE_CONTAINER]).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index 543f6c91513e..2938acf293b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -16,12 +16,12 @@
package com.android.systemui.scene.shared.flag
-import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.Kosmos
import com.google.common.truth.Truth
import org.junit.Test
import org.junit.runner.RunWith
@@ -31,10 +31,11 @@ import org.junit.runner.RunWith
internal class SceneContainerFlagsTest : SysuiTestCase() {
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableSceneContainer
fun isNotEnabled_withoutAconfigFlags() {
Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
+ Truth.assertThat(Kosmos().sceneContainerFlags.isEnabled()).isEqualTo(false)
}
@Test
@@ -42,5 +43,6 @@ internal class SceneContainerFlagsTest : SysuiTestCase() {
fun isEnabled_withAconfigFlags() {
Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(true)
Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(true)
+ Truth.assertThat(Kosmos().sceneContainerFlags.isEnabled()).isEqualTo(true)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt
index 79d3fe9063b7..a1af70b316ee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt
@@ -14,27 +14,38 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel.component.volume.domain.interactor
+package com.android.systemui.settings.brightness.data.repository
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.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@RunWith(AndroidJUnit4::class)
@SmallTest
-class VolumeSliderInteractorTest : SysuiTestCase() {
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorShowingRepositoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
- private val underTest = VolumeSliderInteractor()
+ private val underTest = BrightnessMirrorShowingRepository()
@Test
- fun processVolumeToValue_returnsTranslatedVolume() {
- assertThat(underTest.processVolumeToValue(2, volumeRange)).isEqualTo(20f)
- }
+ fun isShowing_setAndFlow() =
+ kosmos.testScope.runTest {
+ val isShowing by collectLastValue(underTest.isShowing)
+
+ assertThat(isShowing).isFalse()
+
+ underTest.setMirrorShowing(true)
+ assertThat(isShowing).isTrue()
- private companion object {
- val volumeRange = 0..10
- }
+ underTest.setMirrorShowing(false)
+ assertThat(isShowing).isFalse()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt
new file mode 100644
index 000000000000..31d6df250af3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.domain.interactor
+
+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.settings.brightness.data.repository.brightnessMirrorShowingRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorShowingInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val underTest =
+ BrightnessMirrorShowingInteractor(kosmos.brightnessMirrorShowingRepository)
+
+ @Test
+ fun isShowing_setAndFlow() =
+ kosmos.testScope.runTest {
+ val isShowing by collectLastValue(underTest.isShowing)
+
+ assertThat(isShowing).isFalse()
+
+ underTest.setMirrorShowing(true)
+ assertThat(isShowing).isTrue()
+
+ underTest.setMirrorShowing(false)
+ assertThat(isShowing).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt
new file mode 100644
index 000000000000..6de7f403a745
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.ui.binder
+
+import android.content.applicationContext
+import android.view.ContextThemeWrapper
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightnessSliderControllerFactory
+import com.android.systemui.testKosmos
+import com.android.systemui.util.Assert
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorInflaterTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val themedContext =
+ ContextThemeWrapper(kosmos.applicationContext, R.style.Theme_SystemUI_QuickSettings)
+
+ @Test
+ fun inflate_sliderViewAddedToFrame() {
+ Assert.setTestThread(Thread.currentThread())
+
+ val (frame, sliderController) =
+ BrightnessMirrorInflater.inflate(
+ themedContext,
+ kosmos.brightnessSliderControllerFactory
+ )
+
+ assertThat(sliderController.rootView.parent).isSameInstanceAs(frame)
+
+ Assert.setTestThread(null)
+ }
+
+ @Test
+ fun inflate_frameAndSliderViewVisible() {
+ Assert.setTestThread(Thread.currentThread())
+
+ val (frame, sliderController) =
+ BrightnessMirrorInflater.inflate(
+ themedContext,
+ kosmos.brightnessSliderControllerFactory,
+ )
+
+ assertThat(frame.visibility).isEqualTo(View.VISIBLE)
+ assertThat(sliderController.rootView.visibility).isEqualTo(View.VISIBLE)
+
+ Assert.setTestThread(null)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt
new file mode 100644
index 000000000000..09fc6f9ad96d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.ui.viewmodel
+
+import android.content.applicationContext
+import android.content.res.mainResources
+import android.view.ContextThemeWrapper
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
+import com.android.systemui.settings.brightness.ui.binder.BrightnessMirrorInflater
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+import com.android.systemui.settings.brightness.ui.viewModel.LocationAndSize
+import com.android.systemui.settings.brightnessSliderControllerFactory
+import com.android.systemui.testKosmos
+import com.android.systemui.util.Assert
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val themedContext =
+ ContextThemeWrapper(kosmos.applicationContext, R.style.Theme_SystemUI_QuickSettings)
+
+ private val underTest =
+ with(kosmos) {
+ BrightnessMirrorViewModel(
+ brightnessMirrorShowingInteractor,
+ mainResources,
+ brightnessSliderControllerFactory,
+ )
+ }
+
+ @Test
+ fun showHideMirror_isShowing() =
+ with(kosmos) {
+ testScope.runTest {
+ val showing by collectLastValue(underTest.isShowing)
+
+ assertThat(showing).isFalse()
+
+ underTest.showMirror()
+ assertThat(showing).isTrue()
+
+ underTest.hideMirror()
+ assertThat(showing).isFalse()
+ }
+ }
+
+ @Test
+ fun setLocationInWindow_correctLocationAndSize() =
+ with(kosmos) {
+ testScope.runTest {
+ val locationAndSize by collectLastValue(underTest.locationAndSize)
+
+ val x = 20
+ val y = 100
+ val height = 50
+ val width = 200
+ val padding =
+ mainResources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
+
+ val mockView =
+ mock<View> {
+ whenever(getLocationInWindow(any())).then {
+ it.getArgument<IntArray>(0).apply {
+ this[0] = x
+ this[1] = y
+ }
+ Unit
+ }
+
+ whenever(measuredHeight).thenReturn(height)
+ whenever(measuredWidth).thenReturn(width)
+ }
+
+ underTest.setLocationAndSize(mockView)
+
+ assertThat(locationAndSize)
+ .isEqualTo(
+ // Adjust for padding around the view
+ LocationAndSize(
+ yOffset = y - padding,
+ width = width + 2 * padding,
+ height = height + 2 * padding,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun setLocationInWindow_paddingSetToRootView() =
+ with(kosmos) {
+ Assert.setTestThread(Thread.currentThread())
+ val padding =
+ mainResources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
+
+ val view = mock<View>()
+
+ val (_, sliderController) =
+ BrightnessMirrorInflater.inflate(
+ themedContext,
+ brightnessSliderControllerFactory,
+ )
+ underTest.setToggleSlider(sliderController)
+
+ underTest.setLocationAndSize(view)
+
+ with(sliderController.rootView) {
+ assertThat(paddingBottom).isEqualTo(padding)
+ assertThat(paddingTop).isEqualTo(padding)
+ assertThat(paddingLeft).isEqualTo(padding)
+ assertThat(paddingRight).isEqualTo(padding)
+ }
+
+ Assert.setTestThread(null)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 77109d65fadc..7a681b383aad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -39,6 +39,7 @@ import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
@@ -127,6 +128,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsSceneAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
+ brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
mediaDataManager = mediaDataManager,
shadeInteractor = kosmos.shadeInteractor,
footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 3c28c0e4d709..a3cf92986bd6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -32,7 +32,6 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
-import com.android.systemui.statusbar.notification.stack.shared.model.ViewPosition
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationScrollViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
@@ -67,8 +66,8 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
fun updateBounds() =
testScope.runTest {
val radius = MutableStateFlow(32)
- val viewPosition = MutableStateFlow(ViewPosition(0, 0))
- val shape by collectLastValue(appearanceViewModel.shadeScrimShape(radius, viewPosition))
+ val leftOffset = MutableStateFlow(0)
+ val shape by collectLastValue(appearanceViewModel.shadeScrimShape(radius, leftOffset))
placeholderViewModel.onScrimBoundsChanged(
ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f)
@@ -83,7 +82,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
)
)
- viewPosition.value = ViewPosition(200, 15)
+ leftOffset.value = 200
radius.value = 24
placeholderViewModel.onScrimBoundsChanged(
ShadeScrimBounds(left = 210f, top = 200f, right = 300f, bottom = 550f)
@@ -92,7 +91,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
.isEqualTo(
ShadeScrimShape(
bounds =
- ShadeScrimBounds(left = 10f, top = 185f, right = 100f, bottom = 535f),
+ ShadeScrimBounds(left = 10f, top = 200f, right = 100f, bottom = 550f),
topRadius = 24,
bottomRadius = 0
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 509a82da8eb9..8f7a56de0040 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -19,15 +19,22 @@
package com.android.systemui.statusbar.notification.stack.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.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.BrokenWithSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -44,6 +51,8 @@ import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -61,19 +70,36 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class SharedNotificationContainerViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+// SharedNotificationContainerViewModel is only bound when FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT is on
+@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX,
+ )
+ .andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
lateinit var movementFlow: MutableStateFlow<BurnInModel>
val kosmos =
testKosmos().apply {
- fakeFeatureFlagsClassic.apply {
- set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
- }
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
init {
@@ -81,19 +107,28 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
val testScope = kosmos.testScope
- val configurationRepository = kosmos.fakeConfigurationRepository
- val keyguardRepository = kosmos.fakeKeyguardRepository
- val keyguardInteractor = kosmos.keyguardInteractor
- val keyguardRootViewModel = kosmos.keyguardRootViewModel
- val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- val shadeRepository = kosmos.shadeRepository
- val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
- val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper
+ val configurationRepository
+ get() = kosmos.fakeConfigurationRepository
+ val keyguardRepository
+ get() = kosmos.fakeKeyguardRepository
+ val keyguardInteractor
+ get() = kosmos.keyguardInteractor
+ val keyguardRootViewModel
+ get() = kosmos.keyguardRootViewModel
+ val keyguardTransitionRepository
+ get() = kosmos.fakeKeyguardTransitionRepository
+ val shadeRepository
+ get() = kosmos.shadeRepository
+ val sharedNotificationContainerInteractor
+ get() = kosmos.sharedNotificationContainerInteractor
+ val largeScreenHeaderHelper
+ get() = kosmos.mockLargeScreenHeaderHelper
lateinit var underTest: SharedNotificationContainerViewModel
@Before
fun setUp() {
+ assertThat(kosmos.sceneContainerFlags.isEnabled()).isEqualTo(SceneContainerFlag.isEnabled)
overrideResource(R.bool.config_use_split_notification_shade, false)
movementFlow = MutableStateFlow(BurnInModel())
whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow)
@@ -127,38 +162,38 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun validatePaddingTopInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
testScope.runTest {
- mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
overrideResource(R.bool.config_use_split_notification_shade, true)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
- val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ val paddingTop by collectLastValue(underTest.paddingTopDimen)
configurationRepository.onAnyConfigurationChange()
// Should directly use the header height (flagged off value)
- assertThat(dimens!!.paddingTop).isEqualTo(10)
+ assertThat(paddingTop).isEqualTo(10)
}
@Test
+ @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun validatePaddingTopInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
testScope.runTest {
- mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
overrideResource(R.bool.config_use_split_notification_shade, true)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
- val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ val paddingTop by collectLastValue(underTest.paddingTopDimen)
configurationRepository.onAnyConfigurationChange()
// Should directly use the header height (flagged on value)
- assertThat(dimens!!.paddingTop).isEqualTo(5)
+ assertThat(paddingTop).isEqualTo(5)
}
@Test
@@ -168,11 +203,11 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
- val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ val paddingTop by collectLastValue(underTest.paddingTopDimen)
configurationRepository.onAnyConfigurationChange()
- assertThat(dimens!!.paddingTop).isEqualTo(0)
+ assertThat(paddingTop).isEqualTo(0)
}
@Test
@@ -200,9 +235,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX, FLAG_SCENE_CONTAINER)
fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_usesResource() =
testScope.runTest {
- mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
val headerResourceHeight = 50
val headerHelperHeight = 100
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
@@ -219,9 +254,30 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
+ @EnableSceneContainer
+ fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_sceneContainerFlagOn_stillZero() =
+ testScope.runTest {
+ val headerResourceHeight = 50
+ val headerHelperHeight = 100
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ .thenReturn(headerHelperHeight)
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
+ overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+ val dimens by collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+
+ assertThat(dimens!!.marginTop).isEqualTo(0)
+ }
+
+ @Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
+ @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun validateMarginTopWithLargeScreenHeader_refactorFlagOn_usesHelper() =
testScope.runTest {
- mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
val headerResourceHeight = 50
val headerHelperHeight = 100
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
@@ -238,6 +294,27 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @EnableSceneContainer
+ @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
+ fun validateMarginTopWithLargeScreenHeader_sceneContainerFlagOn_stillZero() =
+ testScope.runTest {
+ val headerResourceHeight = 50
+ val headerHelperHeight = 100
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ .thenReturn(headerHelperHeight)
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
+ overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+ val dimens by collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+
+ assertThat(dimens!!.marginTop).isEqualTo(0)
+ }
+
+ @Test
+ @BrokenWithSceneContainer(bugId = 333132830)
fun glanceableHubAlpha_lockscreenToHub() =
testScope.runTest {
val alpha by collectLastValue(underTest.glanceableHubAlpha)
@@ -387,6 +464,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @BrokenWithSceneContainer(bugId = 333132830)
fun isOnLockscreenWithoutShade() =
testScope.runTest {
val isOnLockscreenWithoutShade by collectLastValue(underTest.isOnLockscreenWithoutShade)
@@ -423,6 +501,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @BrokenWithSceneContainer(bugId = 333132830)
fun isOnGlanceableHubWithoutShade() =
testScope.runTest {
val isOnGlanceableHubWithoutShade by
@@ -459,6 +538,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun boundsOnLockscreenNotInSplitShade() =
testScope.runTest {
val bounds by collectLastValue(underTest.bounds)
@@ -479,9 +559,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX, FLAG_SCENE_CONTAINER)
fun boundsOnLockscreenInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
testScope.runTest {
- mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
val bounds by collectLastValue(underTest.bounds)
// When in split shade
@@ -503,13 +583,20 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
runCurrent()
// Top should be equal to bounds (1) - padding adjustment (10)
- assertThat(bounds).isEqualTo(NotificationContainerBounds(top = -9f, bottom = 2f))
+ assertThat(bounds)
+ .isEqualTo(
+ NotificationContainerBounds(
+ top = -9f,
+ bottom = 2f,
+ )
+ )
}
@Test
+ @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun boundsOnLockscreenInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
testScope.runTest {
- mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
val bounds by collectLastValue(underTest.bounds)
// When in split shade
@@ -535,6 +622,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun boundsOnShade() =
testScope.runTest {
val bounds by collectLastValue(underTest.bounds)
@@ -550,6 +638,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun boundsOnQS() =
testScope.runTest {
val bounds by collectLastValue(underTest.bounds)
@@ -594,6 +683,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @BrokenWithSceneContainer(bugId = 333132830)
fun maxNotificationsOnLockscreen_DoesNotUpdateWhenUserInteracting() =
testScope.runTest {
var notificationCount = 10
@@ -630,6 +720,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @BrokenWithSceneContainer(bugId = 333132830)
fun maxNotificationsOnShade() =
testScope.runTest {
val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> 10 }
@@ -649,6 +740,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun translationYUpdatesOnKeyguardForBurnIn() =
testScope.runTest {
val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
@@ -661,6 +753,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun translationYUpdatesOnKeyguard() =
testScope.runTest {
val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
@@ -681,6 +774,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun translationYDoesNotUpdateWhenShadeIsExpanded() =
testScope.runTest {
val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
@@ -701,6 +795,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun updateBounds_fromKeyguardRoot() =
testScope.runTest {
val bounds by collectLastValue(underTest.bounds)
@@ -712,6 +807,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @BrokenWithSceneContainer(bugId = 333132830)
fun alphaOnFullQsExpansion() =
testScope.runTest {
val viewState = ViewStateAccessor()
@@ -819,6 +915,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @BrokenWithSceneContainer(bugId = 333132830)
fun shadeCollapseFadeIn() =
testScope.runTest {
val fadeIn by collectValues(underTest.shadeCollapseFadeIn)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index c8062fb4e724..f0498ded64a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -16,57 +16,20 @@
package com.android.systemui.statusbar.phone
-import android.app.ActivityOptions
import android.app.PendingIntent
import android.content.Intent
-import android.os.Bundle
-import android.os.RemoteException
-import android.os.UserHandle
-import android.view.View
-import android.widget.FrameLayout
-import android.window.SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.ActivityIntentHelper
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityTransitionAnimator
-import com.android.systemui.animation.LaunchableView
-import com.android.systemui.assist.AssistManager
-import com.android.systemui.keyguard.KeyguardViewMediator
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.plugins.ActivityStarter.OnDismissAction
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.shade.data.repository.ShadeAnimationRepository
-import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-import dagger.Lazy
-import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -74,177 +37,22 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
class ActivityStarterImplTest : SysuiTestCase() {
- @Mock private lateinit var centralSurfaces: CentralSurfaces
- @Mock private lateinit var assistManager: AssistManager
- @Mock private lateinit var dozeServiceHost: DozeServiceHost
- @Mock private lateinit var biometricUnlockController: BiometricUnlockController
- @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator
- @Mock private lateinit var shadeController: ShadeController
- @Mock private lateinit var commandQueue: CommandQueue
- @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
- @Mock private lateinit var mActivityTransitionAnimator: ActivityTransitionAnimator
- @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager
- @Mock private lateinit var statusBarWindowController: StatusBarWindowController
- @Mock private lateinit var notifShadeWindowController: NotificationShadeWindowController
- @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
- @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var legacyActivityStarterInternal: LegacyActivityStarterInternalImpl
+ @Mock private lateinit var activityStarterInternal: ActivityStarterInternalImpl
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
- @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
- @Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var activityIntentHelper: ActivityIntentHelper
private lateinit var underTest: ActivityStarterImpl
private val mainExecutor = FakeExecutor(FakeSystemClock())
- private val shadeAnimationInteractor =
- ShadeAnimationInteractorLegacyImpl(ShadeAnimationRepository(), FakeShadeRepository())
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest =
ActivityStarterImpl(
- Lazy { Optional.of(centralSurfaces) },
- Lazy { assistManager },
- Lazy { dozeServiceHost },
- Lazy { biometricUnlockController },
- Lazy { keyguardViewMediator },
- Lazy { shadeController },
- commandQueue,
- shadeAnimationInteractor,
- Lazy { statusBarKeyguardViewManager },
- Lazy { notifShadeWindowController },
- mActivityTransitionAnimator,
- context,
- DISPLAY_ID,
- lockScreenUserManager,
- statusBarWindowController,
- wakefulnessLifecycle,
- keyguardStateController,
- statusBarStateController,
- keyguardUpdateMonitor,
- deviceProvisionedController,
- userTracker,
- activityIntentHelper,
- mainExecutor,
+ statusBarStateController = statusBarStateController,
+ mainExecutor = mainExecutor,
+ legacyActivityStarter = { legacyActivityStarterInternal },
+ activityStarterInternal = { activityStarterInternal },
)
- whenever(userTracker.userHandle).thenReturn(UserHandle.OWNER)
- }
-
- @Test
- fun startPendingIntentDismissingKeyguard_keyguardShowing_dismissWithAction() {
- val pendingIntent = mock(PendingIntent::class.java)
- whenever(pendingIntent.isActivity).thenReturn(true)
- whenever(keyguardStateController.isShowing).thenReturn(true)
- whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
-
- underTest.startPendingIntentDismissingKeyguard(pendingIntent)
- mainExecutor.runAllReady()
-
- verify(statusBarKeyguardViewManager)
- .dismissWithAction(any(OnDismissAction::class.java), eq(null), anyBoolean(), eq(null))
- }
-
- @Test
- fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLs_launchAnimator() {
- val pendingIntent = mock(PendingIntent::class.java)
- val parent = FrameLayout(context)
- val view =
- object : View(context), LaunchableView {
- override fun setShouldBlockVisibilityChanges(block: Boolean) {}
- }
- parent.addView(view)
- val controller = ActivityTransitionAnimator.Controller.fromView(view)
- whenever(pendingIntent.isActivity).thenReturn(true)
- whenever(keyguardStateController.isShowing).thenReturn(true)
- whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
- whenever(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
- .thenReturn(true)
-
- underTest.startPendingIntentMaybeDismissingKeyguard(
- intent = pendingIntent,
- animationController = controller,
- intentSentUiThreadCallback = null,
- )
- mainExecutor.runAllReady()
-
- verify(mActivityTransitionAnimator)
- .startPendingIntentWithAnimation(
- nullable(),
- eq(true),
- nullable(),
- eq(true),
- any(),
- )
- }
-
- fun startPendingIntentDismissingKeyguard_fillInIntentAndExtraOptions_sendAndReturnResult() {
- val pendingIntent = mock(PendingIntent::class.java)
- val fillInIntent = mock(Intent::class.java)
- val parent = FrameLayout(context)
- val view =
- object : View(context), LaunchableView {
- override fun setShouldBlockVisibilityChanges(block: Boolean) {}
- }
- parent.addView(view)
- val controller = ActivityTransitionAnimator.Controller.fromView(view)
- whenever(pendingIntent.isActivity).thenReturn(true)
- whenever(keyguardStateController.isShowing).thenReturn(true)
- whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
- whenever(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
- .thenReturn(false)
-
- // extra activity options to set on pending intent
- val activityOptions = mock(ActivityOptions::class.java)
- activityOptions.splashScreenStyle = SPLASH_SCREEN_STYLE_SOLID_COLOR
- activityOptions.isPendingIntentBackgroundActivityLaunchAllowedByPermission = false
- val bundleCaptor = argumentCaptor<Bundle>()
-
- underTest.startPendingIntentMaybeDismissingKeyguard(
- intent = pendingIntent,
- animationController = controller,
- intentSentUiThreadCallback = null,
- fillInIntent = fillInIntent,
- extraOptions = activityOptions.toBundle(),
- )
- mainExecutor.runAllReady()
-
- // Fill-in intent is passed and options contain extra values specified
- verify(pendingIntent)
- .sendAndReturnResult(
- eq(context),
- eq(0),
- eq(fillInIntent),
- nullable(),
- nullable(),
- nullable(),
- bundleCaptor.capture()
- )
- val options = ActivityOptions.fromBundle(bundleCaptor.value)
- assertThat(options.isPendingIntentBackgroundActivityLaunchAllowedByPermission).isFalse()
- assertThat(options.splashScreenStyle).isEqualTo(SPLASH_SCREEN_STYLE_SOLID_COLOR)
- }
-
- @Test
- fun startPendingIntentDismissingKeyguard_associatedView_getAnimatorController() {
- val pendingIntent = mock(PendingIntent::class.java)
- val associatedView = mock(ExpandableNotificationRow::class.java)
-
- underTest.startPendingIntentDismissingKeyguard(
- intent = pendingIntent,
- intentSentUiThreadCallback = null,
- associatedView = associatedView,
- )
-
- verify(centralSurfaces).getAnimatorControllerFromNotification(associatedView)
- }
-
- @Test
- fun startActivity_noUserHandleProvided_getUserHandle() {
- val intent = mock(Intent::class.java)
-
- underTest.startActivity(intent, false)
-
- verify(userTracker).userHandle
}
@Test
@@ -258,115 +66,9 @@ class ActivityStarterImplTest : SysuiTestCase() {
@Test
fun postStartActivityDismissingKeyguard_intent_postsOnMain() {
- whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
- val intent = mock(Intent::class.java)
-
- underTest.postStartActivityDismissingKeyguard(intent, 0)
+ underTest.postStartActivityDismissingKeyguard(mock(Intent::class.java), 0)
assertThat(mainExecutor.numPending()).isEqualTo(1)
- mainExecutor.runAllReady()
-
- verify(deviceProvisionedController).isDeviceProvisioned
- verify(shadeController).collapseShadeForActivityStart()
- }
-
- @Test
- fun postStartActivityDismissingKeyguard_intent_notDeviceProvisioned_doesNotProceed() {
- whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
- val intent = mock(Intent::class.java)
-
- underTest.postStartActivityDismissingKeyguard(intent, 0)
- mainExecutor.runAllReady()
-
- verify(deviceProvisionedController).isDeviceProvisioned
- verify(shadeController, never()).collapseShadeForActivityStart()
- }
-
- @Test
- fun dismissKeyguardThenExecute_startWakeAndUnlock() {
- whenever(wakefulnessLifecycle.wakefulness)
- .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP)
- whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
- whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
- whenever(dozeServiceHost.isPulsing).thenReturn(true)
-
- underTest.dismissKeyguardThenExecute({ true }, {}, false)
-
- verify(biometricUnlockController)
- .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
- }
-
- @Test
- fun dismissKeyguardThenExecute_keyguardIsShowing_dismissWithAction() {
- val customMessage = "Enter your pin."
- whenever(keyguardStateController.isShowing).thenReturn(true)
-
- underTest.dismissKeyguardThenExecute({ true }, {}, false, customMessage)
-
- verify(statusBarKeyguardViewManager)
- .dismissWithAction(
- any(OnDismissAction::class.java),
- any(Runnable::class.java),
- eq(false),
- eq(customMessage)
- )
- }
-
- @Test
- fun dismissKeyguardThenExecute_awakeDreams() {
- val customMessage = "Enter your pin."
- var dismissActionExecuted = false
- whenever(keyguardStateController.isShowing).thenReturn(false)
- whenever(keyguardUpdateMonitor.isDreaming).thenReturn(true)
-
- underTest.dismissKeyguardThenExecute(
- {
- dismissActionExecuted = true
- true
- },
- {},
- false,
- customMessage
- )
-
- verify(centralSurfaces).awakenDreams()
- assertThat(dismissActionExecuted).isTrue()
- }
-
- @Test
- @Throws(RemoteException::class)
- fun executeRunnableDismissingKeyguard_dreaming_notShowing_awakenDreams() {
- whenever(keyguardStateController.isShowing).thenReturn(false)
- whenever(keyguardStateController.isOccluded).thenReturn(false)
- whenever(keyguardUpdateMonitor.isDreaming).thenReturn(true)
-
- underTest.executeRunnableDismissingKeyguard(
- runnable = {},
- cancelAction = null,
- dismissShade = false,
- afterKeyguardGone = false,
- deferred = false
- )
-
- verify(centralSurfaces, times(1)).awakenDreams()
- }
-
- @Test
- @Throws(RemoteException::class)
- fun executeRunnableDismissingKeyguard_notDreaming_notShowing_doNotAwakenDreams() {
- whenever(keyguardStateController.isShowing).thenReturn(false)
- whenever(keyguardStateController.isOccluded).thenReturn(false)
- whenever(keyguardUpdateMonitor.isDreaming).thenReturn(false)
-
- underTest.executeRunnableDismissingKeyguard(
- runnable = {},
- cancelAction = null,
- dismissShade = false,
- afterKeyguardGone = false,
- deferred = false
- )
-
- verify(centralSurfaces, never()).awakenDreams()
}
@Test
@@ -377,8 +79,4 @@ class ActivityStarterImplTest : SysuiTestCase() {
mainExecutor.runAllReady()
verify(statusBarStateController).setLeaveOpenOnKeyguardHide(true)
}
-
- private companion object {
- private const val DISPLAY_ID = 0
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
new file mode 100644
index 000000000000..b443489f98e2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.app.ActivityOptions
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.Bundle
+import android.os.RemoteException
+import android.os.UserHandle
+import android.view.View
+import android.widget.FrameLayout
+import android.window.SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.data.repository.ShadeAnimationRepository
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LegacyActivityStarterInternalImplTest : SysuiTestCase() {
+ @Mock private lateinit var centralSurfaces: CentralSurfaces
+ @Mock private lateinit var assistManager: AssistManager
+ @Mock private lateinit var dozeServiceHost: DozeServiceHost
+ @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+ @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator
+ @Mock private lateinit var shadeController: ShadeController
+ @Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock private lateinit var activityTransitionAnimator: ActivityTransitionAnimator
+ @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager
+ @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+ @Mock private lateinit var notifShadeWindowController: NotificationShadeWindowController
+ @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityIntentHelper: ActivityIntentHelper
+ private lateinit var underTest: LegacyActivityStarterInternalImpl
+ private val mainExecutor = FakeExecutor(FakeSystemClock())
+ private val shadeAnimationInteractor =
+ ShadeAnimationInteractorLegacyImpl(ShadeAnimationRepository(), FakeShadeRepository())
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ LegacyActivityStarterInternalImpl(
+ centralSurfacesOptLazy = { Optional.of(centralSurfaces) },
+ assistManagerLazy = { assistManager },
+ dozeServiceHostLazy = { dozeServiceHost },
+ biometricUnlockControllerLazy = { biometricUnlockController },
+ keyguardViewMediatorLazy = { keyguardViewMediator },
+ shadeControllerLazy = { shadeController },
+ commandQueue = commandQueue,
+ shadeAnimationInteractor = shadeAnimationInteractor,
+ statusBarKeyguardViewManagerLazy = { statusBarKeyguardViewManager },
+ notifShadeWindowControllerLazy = { notifShadeWindowController },
+ activityTransitionAnimator = activityTransitionAnimator,
+ context = context,
+ displayId = DISPLAY_ID,
+ lockScreenUserManager = lockScreenUserManager,
+ statusBarWindowController = statusBarWindowController,
+ wakefulnessLifecycle = wakefulnessLifecycle,
+ keyguardStateController = keyguardStateController,
+ statusBarStateController = statusBarStateController,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ deviceProvisionedController = deviceProvisionedController,
+ userTracker = userTracker,
+ activityIntentHelper = activityIntentHelper,
+ mainExecutor = mainExecutor,
+ )
+ whenever(userTracker.userHandle).thenReturn(UserHandle.OWNER)
+ }
+
+ @Test
+ fun startPendingIntentDismissingKeyguard_keyguardShowing_dismissWithAction() {
+ val pendingIntent = mock(PendingIntent::class.java)
+ whenever(pendingIntent.isActivity).thenReturn(true)
+ whenever(keyguardStateController.isShowing).thenReturn(true)
+ whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+
+ underTest.startPendingIntentDismissingKeyguard(pendingIntent)
+ mainExecutor.runAllReady()
+
+ verify(statusBarKeyguardViewManager)
+ .dismissWithAction(any(OnDismissAction::class.java), eq(null), anyBoolean(), eq(null))
+ }
+
+ @Test
+ fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLs_launchAnimator() {
+ val pendingIntent = mock(PendingIntent::class.java)
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
+ whenever(pendingIntent.isActivity).thenReturn(true)
+ whenever(keyguardStateController.isShowing).thenReturn(true)
+ whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+ whenever(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ .thenReturn(true)
+
+ startPendingIntentMaybeDismissingKeyguard(
+ intent = pendingIntent,
+ animationController = controller,
+ intentSentUiThreadCallback = null,
+ )
+ mainExecutor.runAllReady()
+
+ verify(activityTransitionAnimator)
+ .startPendingIntentWithAnimation(
+ nullable(),
+ eq(true),
+ nullable(),
+ eq(true),
+ any(),
+ )
+ }
+
+ fun startPendingIntentDismissingKeyguard_fillInIntentAndExtraOptions_sendAndReturnResult() {
+ val pendingIntent = mock(PendingIntent::class.java)
+ val fillInIntent = mock(Intent::class.java)
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
+ whenever(pendingIntent.isActivity).thenReturn(true)
+ whenever(keyguardStateController.isShowing).thenReturn(true)
+ whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+ whenever(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+
+ // extra activity options to set on pending intent
+ val activityOptions = mock(ActivityOptions::class.java)
+ activityOptions.splashScreenStyle = SPLASH_SCREEN_STYLE_SOLID_COLOR
+ activityOptions.isPendingIntentBackgroundActivityLaunchAllowedByPermission = false
+ val bundleCaptor = argumentCaptor<Bundle>()
+
+ startPendingIntentMaybeDismissingKeyguard(
+ intent = pendingIntent,
+ animationController = controller,
+ intentSentUiThreadCallback = null,
+ fillInIntent = fillInIntent,
+ extraOptions = activityOptions.toBundle(),
+ )
+ mainExecutor.runAllReady()
+
+ // Fill-in intent is passed and options contain extra values specified
+ verify(pendingIntent)
+ .sendAndReturnResult(
+ eq(context),
+ eq(0),
+ eq(fillInIntent),
+ nullable(),
+ nullable(),
+ nullable(),
+ bundleCaptor.capture()
+ )
+ val options = ActivityOptions.fromBundle(bundleCaptor.value)
+ assertThat(options.isPendingIntentBackgroundActivityLaunchAllowedByPermission).isFalse()
+ assertThat(options.splashScreenStyle).isEqualTo(SPLASH_SCREEN_STYLE_SOLID_COLOR)
+ }
+
+ @Test
+ fun startPendingIntentDismissingKeyguard_associatedView_getAnimatorController() {
+ val pendingIntent = mock(PendingIntent::class.java)
+ val associatedView = mock(ExpandableNotificationRow::class.java)
+
+ underTest.startPendingIntentDismissingKeyguard(
+ intent = pendingIntent,
+ intentSentUiThreadCallback = null,
+ associatedView = associatedView,
+ )
+
+ verify(centralSurfaces).getAnimatorControllerFromNotification(associatedView)
+ }
+
+ @Test
+ fun startActivity_noUserHandleProvided_getUserHandle() {
+ val intent = mock(Intent::class.java)
+
+ underTest.startActivity(intent, false, null, false, null)
+
+ verify(userTracker).userHandle
+ }
+
+ @Test
+ fun dismissKeyguardThenExecute_startWakeAndUnlock() {
+ whenever(wakefulnessLifecycle.wakefulness)
+ .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP)
+ whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
+ whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
+ whenever(dozeServiceHost.isPulsing).thenReturn(true)
+
+ underTest.dismissKeyguardThenExecute({ true }, {}, false)
+
+ verify(biometricUnlockController)
+ .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+ }
+
+ @Test
+ fun dismissKeyguardThenExecute_keyguardIsShowing_dismissWithAction() {
+ val customMessage = "Enter your pin."
+ whenever(keyguardStateController.isShowing).thenReturn(true)
+
+ underTest.dismissKeyguardThenExecute({ true }, {}, false, customMessage)
+
+ verify(statusBarKeyguardViewManager)
+ .dismissWithAction(
+ any(OnDismissAction::class.java),
+ any(Runnable::class.java),
+ eq(false),
+ eq(customMessage)
+ )
+ }
+
+ @Test
+ fun dismissKeyguardThenExecute_awakeDreams() {
+ val customMessage = "Enter your pin."
+ var dismissActionExecuted = false
+ whenever(keyguardStateController.isShowing).thenReturn(false)
+ whenever(keyguardUpdateMonitor.isDreaming).thenReturn(true)
+
+ underTest.dismissKeyguardThenExecute(
+ {
+ dismissActionExecuted = true
+ true
+ },
+ {},
+ false,
+ customMessage
+ )
+
+ verify(centralSurfaces).awakenDreams()
+ assertThat(dismissActionExecuted).isTrue()
+ }
+
+ @Test
+ @Throws(RemoteException::class)
+ fun executeRunnableDismissingKeyguard_dreaming_notShowing_awakenDreams() {
+ whenever(keyguardStateController.isShowing).thenReturn(false)
+ whenever(keyguardStateController.isOccluded).thenReturn(false)
+ whenever(keyguardUpdateMonitor.isDreaming).thenReturn(true)
+
+ underTest.executeRunnableDismissingKeyguard(
+ runnable = {},
+ cancelAction = null,
+ dismissShade = false,
+ afterKeyguardGone = false,
+ deferred = false
+ )
+
+ verify(centralSurfaces, times(1)).awakenDreams()
+ }
+
+ @Test
+ @Throws(RemoteException::class)
+ fun executeRunnableDismissingKeyguard_notDreaming_notShowing_doNotAwakenDreams() {
+ whenever(keyguardStateController.isShowing).thenReturn(false)
+ whenever(keyguardStateController.isOccluded).thenReturn(false)
+ whenever(keyguardUpdateMonitor.isDreaming).thenReturn(false)
+
+ underTest.executeRunnableDismissingKeyguard(
+ runnable = {},
+ cancelAction = null,
+ dismissShade = false,
+ afterKeyguardGone = false,
+ deferred = false
+ )
+
+ verify(centralSurfaces, never()).awakenDreams()
+ }
+
+ private fun startPendingIntentMaybeDismissingKeyguard(
+ intent: PendingIntent,
+ intentSentUiThreadCallback: Runnable?,
+ animationController: ActivityTransitionAnimator.Controller?,
+ fillInIntent: Intent? = null,
+ extraOptions: Bundle? = null,
+ ) {
+ underTest.startPendingIntentDismissingKeyguard(
+ intent = intent,
+ intentSentUiThreadCallback = intentSentUiThreadCallback,
+ animationController = animationController,
+ showOverLockscreen = true,
+ fillInIntent = fillInIntent,
+ extraOptions = extraOptions,
+ )
+ }
+
+ private companion object {
+ private const val DISPLAY_ID = 0
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartableTest.kt
new file mode 100644
index 000000000000..8bb36724d1d8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartableTest.kt
@@ -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.systemui.volume.domain.startable
+
+import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
+import com.android.internal.logging.uiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.audioModeInteractor
+import com.android.systemui.volume.audioRepository
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class AudioModeLoggerStartableTest : SysuiTestCase() {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: AudioModeLoggerStartable
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ underTest =
+ AudioModeLoggerStartable(
+ applicationCoroutineScope,
+ uiEventLogger,
+ audioModeInteractor
+ )
+ }
+ }
+
+ @Test
+ fun audioMode_inCall() {
+ with(kosmos) {
+ testScope.runTest {
+ audioRepository.setMode(AudioManager.MODE_IN_CALL)
+
+ underTest.start()
+ runCurrent()
+
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(VolumePanelUiEvent.VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_CALLING.id)
+ }
+ }
+ }
+
+ @Test
+ fun audioMode_notInCall() {
+ with(kosmos) {
+ testScope.runTest {
+ audioRepository.setMode(AudioManager.MODE_NORMAL)
+
+ underTest.start()
+ runCurrent()
+
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(VolumePanelUiEvent.VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_NORMAL.id)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
index e31cdcd82e7e..dc9613904e4e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
@@ -72,7 +72,7 @@ class AncSliceRepositoryTest : SysuiTestCase() {
testScope.runTest {
localMediaRepository.updateCurrentConnectedDevice(null)
- val slice by collectLastValue(underTest.ancSlice(1))
+ val slice by collectLastValue(underTest.ancSlice(1, false, false))
runCurrent()
assertThat(slice).isNull()
@@ -86,7 +86,7 @@ class AncSliceRepositoryTest : SysuiTestCase() {
testScope.runTest {
localMediaRepository.updateCurrentConnectedDevice(createMediaDevice())
- val slice by collectLastValue(underTest.ancSlice(1))
+ val slice by collectLastValue(underTest.ancSlice(1, false, false))
runCurrent()
assertThat(slice).isNotNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt
index 53f0bc9ddb51..81e6ac412404 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt
@@ -24,6 +24,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.volume.panel.component.anc.FakeSliceFactory
import com.android.systemui.volume.panel.component.anc.ancSliceRepository
+import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -57,10 +58,10 @@ class AncSliceInteractorTest : SysuiTestCase() {
FakeSliceFactory.createSlice(hasError = true, hasSliceItem = true)
)
- val slice by collectLastValue(underTest.ancSlice)
+ val slice by collectLastValue(underTest.ancSlices)
runCurrent()
- assertThat(slice).isNull()
+ assertThat(slice).isInstanceOf(AncSlices.Unavailable::class.java)
}
}
}
@@ -74,10 +75,10 @@ class AncSliceInteractorTest : SysuiTestCase() {
FakeSliceFactory.createSlice(hasError = false, hasSliceItem = false)
)
- val slice by collectLastValue(underTest.ancSlice)
+ val slice by collectLastValue(underTest.ancSlices)
runCurrent()
- assertThat(slice).isNull()
+ assertThat(slice).isInstanceOf(AncSlices.Unavailable::class.java)
}
}
}
@@ -91,10 +92,10 @@ class AncSliceInteractorTest : SysuiTestCase() {
FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true)
)
- val slice by collectLastValue(underTest.ancSlice)
+ val slice by collectLastValue(underTest.ancSlices)
runCurrent()
- assertThat(slice).isNotNull()
+ assertThat(slice).isInstanceOf(AncSlices.Ready::class.java)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
index 2cc1ad335535..27a813fb149e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
@@ -21,6 +21,8 @@ import android.content.Intent
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
+import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -29,6 +31,7 @@ import com.android.systemui.plugins.activityStarter
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import com.android.systemui.volume.panel.volumePanelViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -58,7 +61,10 @@ class BottomBarViewModelTest : SysuiTestCase() {
private lateinit var underTest: BottomBarViewModel
private fun initUnderTest() {
- underTest = with(kosmos) { BottomBarViewModel(activityStarter, volumePanelViewModel) }
+ underTest =
+ with(kosmos) {
+ BottomBarViewModel(activityStarter, volumePanelViewModel, uiEventLogger)
+ }
}
@Test
@@ -96,6 +102,8 @@ class BottomBarViewModelTest : SysuiTestCase() {
/* userHandle = */ eq(null),
)
assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_SOUND_SETTINGS)
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(VolumePanelUiEvent.VOLUME_PANEL_SOUND_SETTINGS_CLICKED.id)
activityStartedCaptor.value.onActivityStarted(ActivityManager.START_SUCCESS)
val volumePanelState by collectLastValue(volumePanelViewModel.volumePanelState)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
index 610195f5e87e..fdeded8422d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.volume.panel.component.captioning.ui.viewmodel
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -45,7 +46,12 @@ class CaptioningViewModelTest : SysuiTestCase() {
fun setup() {
underTest =
with(kosmos) {
- CaptioningViewModel(context, captioningInteractor, testScope.backgroundScope)
+ CaptioningViewModel(
+ context,
+ captioningInteractor,
+ testScope.backgroundScope,
+ uiEventLogger,
+ )
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
index ec55c75d4ae5..da0a2295143b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
@@ -46,7 +46,10 @@ class MediaOutputAvailabilityCriteriaTest : SysuiTestCase() {
@Before
fun setup() {
- underTest = MediaOutputAvailabilityCriteria(kosmos.audioModeInteractor)
+ underTest =
+ MediaOutputAvailabilityCriteria(
+ kosmos.audioModeInteractor,
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index 462f36d73138..30524d93dc02 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -22,6 +22,7 @@ import android.media.session.PlaybackState
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -64,6 +65,7 @@ class MediaOutputViewModelTest : SysuiTestCase() {
mediaOutputActionsInteractor,
mediaDeviceSessionInteractor,
mediaOutputInteractor,
+ uiEventLogger,
)
with(context.orCreateTestableResources) {
diff --git a/packages/SystemUI/res/drawable/ic_bugreport.xml b/packages/SystemUI/res/drawable/ic_bugreport.xml
new file mode 100644
index 000000000000..ed1c6c723543
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_bugreport.xml
@@ -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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,14h4v2h-4z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,10h4v2h-4z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_noise_aware.xml b/packages/SystemUI/res/drawable/ic_noise_aware.xml
deleted file mode 100644
index 54826414f3f7..000000000000
--- a/packages/SystemUI/res/drawable/ic_noise_aware.xml
+++ /dev/null
@@ -1,26 +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.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="960"
- android:viewportHeight="960"
- android:tint="?attr/colorControlNormal">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M440,82Q450,81 460,80.5Q470,80 480,80Q491,80 500.5,80.5Q510,81 520,82L520,162Q510,160 500.5,160Q491,160 480,160Q469,160 459.5,160Q450,160 440,162L440,82ZM272,138Q289,127 306.5,119Q324,111 343,104L378,176Q358,182 340.5,190.5Q323,199 306,210L272,138ZM654,210Q637,199 619.5,190.5Q602,182 582,176L617,104Q636,111 653.5,119Q671,127 688,138L654,210ZM753,311Q742,294 729,278.5Q716,263 702,249L765,199Q779,213 792,228.5Q805,244 816,261L753,311ZM143,263Q154,246 166.5,230.5Q179,215 193,201L256,251Q242,265 229.5,280.5Q217,296 206,313L143,263ZM83,428Q85,408 90,388.5Q95,369 101,350L180,368Q173,387 168.5,406.5Q164,426 162,446L83,428ZM799,449Q797,429 792.5,409Q788,389 781,370L859,352Q865,371 870,390.5Q875,410 877,430L799,449ZM781,590Q788,571 792,552Q796,533 798,513L877,531Q875,551 870,570.5Q865,590 859,609L781,590ZM162,514Q164,534 168.5,553.5Q173,573 180,592L101,610Q95,591 90,571.5Q85,552 83,532L162,514ZM705,708Q719,694 731,678.5Q743,663 754,646L818,696Q807,713 794.5,728.5Q782,744 768,758L705,708ZM194,760Q180,746 167.5,730Q155,714 144,697L206,647Q217,664 229.5,680Q242,696 256,710L194,760ZM583,783Q603,776 620,768Q637,760 654,749L689,821Q672,832 654.5,840.5Q637,849 618,856L583,783ZM344,857Q325,850 307,841.5Q289,833 272,822L307,750Q324,761 341.5,769.5Q359,778 379,784L344,857ZM480,880Q470,880 460,879.5Q450,879 440,878L440,798Q453,800 480,800Q491,800 500.5,800Q510,800 520,798L520,878Q510,879 500.5,879.5Q491,880 480,880ZM520,720Q482,720 450.5,697Q419,674 406,638Q403,629 399.5,620.5Q396,612 389,605L334,550Q308,524 294,490.5Q280,457 280,420Q280,345 332.5,292.5Q385,240 460,240Q529,240 580,285.5Q631,331 639,400L558,400Q551,365 523.5,342.5Q496,320 460,320Q418,320 389,349Q360,378 360,420Q360,440 368,459.5Q376,479 391,494L445,548Q459,562 467.5,578.5Q476,595 482,612Q487,625 497,632.5Q507,640 520,640Q537,640 548.5,628.5Q560,617 560,600L640,600Q640,650 605.5,685Q571,720 520,720ZM540,560Q515,560 497.5,542.5Q480,525 480,500Q480,474 497.5,457Q515,440 540,440Q566,440 583,457Q600,474 600,500Q600,525 583,542.5Q566,560 540,560Z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml b/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml
index 3c31861b28ae..a706c650dd65 100644
--- a/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml
+++ b/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml
@@ -19,9 +19,4 @@
<solid android:color="@color/tv_volume_dialog_accent" />
<size android:width="@dimen/tv_volume_seek_bar_thumb_diameter"
android:height="@dimen/tv_volume_seek_bar_thumb_diameter" />
- <stroke android:width="@dimen/tv_volume_seek_bar_thumb_focus_ring_width"
- android:color="@color/tv_volume_dialog_seek_thumb_focus_ring"/>
- <item name="android:shadowColor">@color/tv_volume_dialog_seek_thumb_shadow</item>
- <item name="android:shadowRadius">@dimen/tv_volume_seek_bar_thumb_shadow_radius</item>
- <item name="android:shadowDy">@dimen/tv_volume_seek_bar_thumb_shadow_dy</item>
</shape>
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
index 4aa609207312..8e3cf4d9b446 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -70,6 +70,7 @@
android:layout_height="@dimen/biometric_prompt_logo_size"
android:layout_gravity="center"
android:scaleType="fitXY"
+ android:importantForAccessibility="no"
app:layout_constraintBottom_toTopOf="@+id/logo_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
diff --git a/packages/SystemUI/res/layout/record_issue_dialog.xml b/packages/SystemUI/res/layout/record_issue_dialog.xml
index 53ad9f157a2e..30d7b0ae739a 100644
--- a/packages/SystemUI/res/layout/record_issue_dialog.xml
+++ b/packages/SystemUI/res/layout/record_issue_dialog.xml
@@ -54,6 +54,7 @@
android:layout_weight="0"
android:src="@drawable/ic_screenrecord"
app:tint="?androidprv:attr/materialColorOnSurface"
+ android:importantForAccessibility="no"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/screenrecord_option_padding" />
@@ -78,4 +79,44 @@
android:layout_weight="0"
android:contentDescription="@string/quick_settings_screen_record_label" />
</LinearLayout>
+
+ <!-- Bug Report Switch -->
+ <LinearLayout
+ android:id="@+id/bugreport_switch_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:layout_width="@dimen/screenrecord_option_icon_size"
+ android:layout_height="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="0"
+ android:src="@drawable/ic_bugreport"
+ app:tint="?androidprv:attr/materialColorOnSurface"
+ android:importantForAccessibility="no"
+ android:layout_gravity="center"
+ android:layout_marginEnd="@dimen/screenrecord_option_padding" />
+
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="1"
+ android:layout_gravity="fill_vertical"
+ android:gravity="center"
+ android:text="@string/qs_record_issue_bug_report"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:importantForAccessibility="no"/>
+
+ <Switch
+ android:id="@+id/bugreport_switch"
+ android:layout_width="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_gravity="fill_vertical"
+ android:layout_weight="0"
+ android:contentDescription="@string/qs_record_issue_bug_report" />
+ </LinearLayout>
</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values-land-television/dimens.xml b/packages/SystemUI/res/values-land-television/dimens.xml
index 52f591f1bb79..d3bafbca4bb7 100644
--- a/packages/SystemUI/res/values-land-television/dimens.xml
+++ b/packages/SystemUI/res/values-land-television/dimens.xml
@@ -28,9 +28,6 @@
<dimen name="tv_volume_dialog_bubble_size">36dp</dimen>
<dimen name="tv_volume_dialog_row_padding">6dp</dimen>
<dimen name="tv_volume_number_text_size">16sp</dimen>
- <dimen name="tv_volume_seek_bar_thumb_diameter">24dp</dimen>
- <dimen name="tv_volume_seek_bar_thumb_focus_ring_width">8dp</dimen>
+ <dimen name="tv_volume_seek_bar_thumb_diameter">16dp</dimen>
<dimen name="tv_volume_icons_size">20dp</dimen>
- <dimen name="tv_volume_seek_bar_thumb_shadow_radius">4.0</dimen>
- <dimen name="tv_volume_seek_bar_thumb_shadow_dy">4.0</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/colors_tv.xml b/packages/SystemUI/res/values/colors_tv.xml
index 2bab3cb70715..5d45607878d3 100644
--- a/packages/SystemUI/res/values/colors_tv.xml
+++ b/packages/SystemUI/res/values/colors_tv.xml
@@ -28,8 +28,6 @@
<color name="red">#FFCC0000</color>
<color name="tv_volume_dialog_circle">#08FFFFFF</color>
- <color name="tv_volume_dialog_seek_thumb_focus_ring">#1AFFFFFF</color>
- <color name="tv_volume_dialog_seek_thumb_shadow">#40000000</color>
<color name="tv_volume_dialog_seek_bar_background">#A03C4043</color>
<color name="tv_volume_dialog_seek_bar_fill">#FFF8F9FA</color>
<color name="tv_volume_dialog_accent">#FFDADCE0</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b0b548295b71..af327d2b7791 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -2001,4 +2001,7 @@
<!-- SliceView grid gutter for ANC Slice -->
<dimen name="abc_slice_grid_gutter">0dp</dimen>
+ <!-- SliceView icon size -->
+ <dimen name="abc_slice_big_pic_min_height">64dp</dimen>
+ <dimen name="abc_slice_big_pic_max_height">64dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 44cbdbc2c75b..af661aa172c7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -872,6 +872,8 @@
<string name="qs_record_issue_start">Start</string>
<!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
<string name="qs_record_issue_stop">Stop</string>
+ <!-- QuickSettings: Should user take a bugreport or only share trace files [CHAR LIMIT=20] -->
+ <string name="qs_record_issue_bug_report">Bug Report</string>
<!-- QuickSettings: Issue Type Drop down options in Record Issue Start Dialog [CHAR LIMIT=50] -->
<string name="qs_record_issue_dropdown_header">What part of your device experience was affected?</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9da4f79e33df..2c9006e50f92 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -230,6 +230,8 @@
<style name="TextAppearance.AuthCredential.Indicator" parent="TextAppearance.Material3.BodyMedium">
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
<item name="android:marqueeRepeatLimit">marquee_forever</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:ellipsize">marquee</item>
</style>
<style name="TextAppearance.AuthCredential.Error">
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 70182c16a093..7c454078ad0e 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -15,6 +15,7 @@
*/
package com.android.keyguard
+import android.os.Trace
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -31,7 +32,6 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.tracing.coroutines.launch
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.customization.R
import com.android.systemui.dagger.qualifiers.Background
@@ -66,7 +66,6 @@ import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.Executor
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
@@ -74,6 +73,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
/**
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
@@ -91,7 +91,6 @@ constructor(
@DisplaySpecific private val resources: Resources,
private val context: Context,
@Main private val mainExecutor: DelayableExecutor,
- @Main private val mainImmediateDispatcher: CoroutineDispatcher,
@Background private val bgExecutor: Executor,
private val clockBuffers: ClockMessageBuffers,
private val featureFlags: FeatureFlagsClassic,
@@ -426,7 +425,7 @@ constructor(
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
zenModeController.addCallback(zenModeCallback)
disposableHandle =
- parent.repeatWhenAttached(mainImmediateDispatcher) {
+ parent.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
listenForDozing(this)
if (MigrateClocksToBlueprint.isEnabled) {
@@ -522,8 +521,12 @@ constructor(
private fun handleDoze(doze: Float) {
dozeAmount = doze
clock?.run {
+ Trace.beginSection("$TAG#smallClock.animations.doze")
smallClock.animations.doze(dozeAmount)
+ Trace.endSection()
+ Trace.beginSection("$TAG#largeClock.animations.doze")
largeClock.animations.doze(dozeAmount)
+ Trace.endSection()
}
smallTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
largeTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
@@ -531,19 +534,17 @@ constructor(
@VisibleForTesting
internal fun listenForDozeAmount(scope: CoroutineScope): Job {
- return scope.launch("$TAG#listenForDozeAmount") {
- keyguardInteractor.dozeAmount.collect { handleDoze(it) }
- }
+ return scope.launch { keyguardInteractor.dozeAmount.collect { handleDoze(it) } }
}
@VisibleForTesting
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
- return scope.launch("$TAG#listenForDozeAmountTransition") {
+ return scope.launch {
merge(
- keyguardTransitionInteractor.aodToLockscreenTransition.map { step ->
+ keyguardTransitionInteractor.transition(AOD, LOCKSCREEN).map { step ->
step.copy(value = 1f - step.value)
},
- keyguardTransitionInteractor.lockscreenToAodTransition,
+ keyguardTransitionInteractor.transition(LOCKSCREEN, AOD),
).filter {
it.transitionState != TransitionState.FINISHED
}
@@ -556,7 +557,7 @@ constructor(
*/
@VisibleForTesting
internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
- return scope.launch("$TAG#listenForAnyStateToAodTransition") {
+ return scope.launch {
keyguardTransitionInteractor
.transitionStepsToState(AOD)
.filter { it.transitionState == TransitionState.STARTED }
@@ -567,7 +568,7 @@ constructor(
@VisibleForTesting
internal fun listenForDozing(scope: CoroutineScope): Job {
- return scope.launch("$TAG#listenForDozing") {
+ return scope.launch {
combine(
keyguardInteractor.dozeAmount,
keyguardInteractor.isDozing,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 25d771308aea..c509356c4e63 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -83,9 +83,9 @@ import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -101,6 +101,8 @@ import com.android.systemui.util.ViewController;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
+import dagger.Lazy;
+
import java.io.File;
import java.util.Arrays;
import java.util.Optional;
@@ -108,7 +110,6 @@ import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Provider;
-import dagger.Lazy;
import kotlinx.coroutines.Job;
/** Controller for {@link KeyguardSecurityContainer} */
@@ -310,7 +311,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
*/
@Override
public void finish(int targetUserId) {
- if (!mFeatureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (!RefactorKeyguardDismissIntent.isEnabled()) {
// If there's a pending runnable because the user interacted with a widget
// and we're leaving keyguard, then run it.
boolean deferKeyguardDone = false;
@@ -649,7 +650,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
* @param action callback to be invoked when keyguard disappear animation completes.
*/
public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) {
- if (mFeatureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (RefactorKeyguardDismissIntent.isEnabled()) {
return;
}
if (mCancelAction != null) {
@@ -943,7 +944,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mUiEventLogger.log(uiEvent, getSessionId());
}
- if (mFeatureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (RefactorKeyguardDismissIntent.isEnabled()) {
if (authenticatedWithPrimaryAuth) {
mPrimaryBouncerInteractor.get()
.notifyKeyguardAuthenticatedPrimaryAuth(targetUserId);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 8c51a4e0ce66..4987724ea7b2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -225,7 +225,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
protected static final int BIOMETRIC_STATE_STOPPED = 0;
/** Biometric authentication state: Listening. */
- private static final int BIOMETRIC_STATE_RUNNING = 1;
+ protected static final int BIOMETRIC_STATE_RUNNING = 1;
/**
* Biometric authentication: Cancelling and waiting for the relevant biometric service to
@@ -1145,7 +1145,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
if (getUserCanSkipBouncer(userId)) {
mTrustManager.unlockedByBiometricForUser(userId, FACE);
}
- updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
mLogger.d("onFaceAuthenticated");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1156,6 +1155,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
+ // Intentionally update the fingerprint running state after sending the
+ // onBiometricAuthenticated callback to listeners. Updating the fingerprint listening state
+ // can update the state of the device which listeners to the callback may rely on.
+ // For example, the alternate bouncer visibility state or udfps finger down state.
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+
// Only authenticate face once when assistant is visible
mAssistantVisible = false;
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 3e03fb8dfaff..b8af59d78942 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -401,8 +401,6 @@ public class ScreenDecorations implements
mExecutor = mThreadFactory.buildDelayableExecutorOnHandler(mHandler);
mExecutor.execute(this::startOnScreenDecorationsThread);
mDotViewController.setUiExecutor(mExecutor);
- mJavaAdapter.alwaysCollectFlow(mFacePropertyRepository.getSensorLocation(),
- this::onFaceSensorLocationChanged);
mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME,
() -> new ScreenDecorCommand(mScreenDecorCommandCallback));
}
@@ -579,6 +577,8 @@ public class ScreenDecorations implements
};
mDisplayTracker.addDisplayChangeCallback(mDisplayListener, new HandlerExecutor(mHandler));
updateConfiguration();
+ mJavaAdapter.alwaysCollectFlow(mFacePropertyRepository.getSensorLocation(),
+ this::onFaceSensorLocationChanged);
Trace.endSection();
}
@@ -1320,10 +1320,18 @@ public class ScreenDecorations implements
@VisibleForTesting
void onFaceSensorLocationChanged(Point location) {
mLogger.onSensorLocationChanged();
+
if (mExecutor != null) {
mExecutor.execute(
- () -> updateOverlayProviderViews(
- new Integer[]{mFaceScanningViewId}));
+ () -> {
+ if (getOverlayView(mFaceScanningViewId) == null) {
+ // face sensor location was just initialized
+ setupDecorations();
+ } else {
+ updateOverlayProviderViews(new Integer[]{mFaceScanningViewId});
+ }
+ }
+ );
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
index 0f4d63c7ebcc..7e96e48545ea 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -149,7 +149,7 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp
if (mState == STATE_ENABLING || mState == STATE_DISABLING) {
mValueAnimator.cancel();
}
- mController.enableWindowMagnificationInternal(scale, centerX, centerY,
+ mController.updateWindowMagnificationInternal(scale, centerX, centerY,
mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
updateState();
return;
@@ -159,7 +159,7 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp
if (mEndSpec.equals(mStartSpec)) {
if (mState == STATE_DISABLED) {
- mController.enableWindowMagnificationInternal(scale, centerX, centerY,
+ mController.updateWindowMagnificationInternal(scale, centerX, centerY,
mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
} else if (mState == STATE_ENABLING || mState == STATE_DISABLING) {
mValueAnimator.cancel();
@@ -306,7 +306,7 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp
// If the animation is playing backwards, mStartSpec will be the final spec we would
// like to reach.
AnimationSpec spec = isReverse ? mStartSpec : mEndSpec;
- mController.enableWindowMagnificationInternal(
+ mController.updateWindowMagnificationInternal(
spec.mScale, spec.mCenterX, spec.mCenterY,
mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
@@ -358,7 +358,7 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp
mStartSpec.mCenterX + (mEndSpec.mCenterX - mStartSpec.mCenterX) * fract;
final float centerY =
mStartSpec.mCenterY + (mEndSpec.mCenterY - mStartSpec.mCenterY) * fract;
- mController.enableWindowMagnificationInternal(sentScale, centerX, centerY,
+ mController.updateWindowMagnificationInternal(sentScale, centerX, centerY,
mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index d65cd5c09dcf..a847c3d510b1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -148,7 +148,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
/**
* SourceBound is the bound of the magnified region which projects the magnified content.
* SourceBound's center is equal to the parameters centerX and centerY in
- * {@link WindowMagnificationController#enableWindowMagnificationInternal(float, float, float)}}
+ * {@link WindowMagnificationController#updateWindowMagnificationInternal(float, float, float)}}
* but it is calculated from {@link #mMagnificationFrame}'s center in the runtime.
*/
private final Rect mSourceBounds = new Rect();
@@ -566,7 +566,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
// window size changed not caused by rotation.
if (isActivated() && reCreateWindow) {
deleteWindowMagnification();
- enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN);
+ updateWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN);
}
}
@@ -1317,7 +1317,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
/**
- * Wraps {@link WindowMagnificationController#enableWindowMagnificationInternal(float, float,
+ * Wraps {@link WindowMagnificationController#updateWindowMagnificationInternal(float, float,
* float, float, float)}
* with transition animation. If the window magnification is not enabled, the scale will start
* from 1.0 and the center won't be changed during the animation. If animator is
@@ -1344,10 +1344,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
/**
- * Enables window magnification with specified parameters. If the given scale is <strong>less
- * than or equal to 1.0f</strong>, then
+ * Updates window magnification status with specified parameters. If the given scale is
+ * <strong>less than 1.0f</strong>, then
* {@link WindowMagnificationController#deleteWindowMagnification()} will be called instead to
- * be consistent with the behavior of display magnification.
+ * be consistent with the behavior of display magnification. If the given scale is
+ * <strong>larger than or equal to 1.0f</strong>, and the window magnification is not activated
+ * yet, window magnification will be enabled.
*
* @param scale the target scale, or {@link Float#NaN} to leave unchanged
* @param centerX the screen-relative X coordinate around which to center for magnification,
@@ -1355,16 +1357,17 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
* @param centerY the screen-relative Y coordinate around which to center for magnification,
* or {@link Float#NaN} to leave unchanged.
*/
- void enableWindowMagnificationInternal(float scale, float centerX, float centerY) {
- enableWindowMagnificationInternal(scale, centerX, centerY, Float.NaN, Float.NaN);
+ void updateWindowMagnificationInternal(float scale, float centerX, float centerY) {
+ updateWindowMagnificationInternal(scale, centerX, centerY, Float.NaN, Float.NaN);
}
/**
- * Enables window magnification with specified parameters. If the given scale is <strong>less
- * than 1.0f</strong>, then
+ * Updates window magnification status with specified parameters. If the given scale is
+ * <strong>less than 1.0f</strong>, then
* {@link WindowMagnificationController#deleteWindowMagnification()} will be called instead to
- * be consistent with the behavior of display magnification.
- *
+ * be consistent with the behavior of display magnification. If the given scale is
+ * <strong>larger than or equal to 1.0f</strong>, and the window magnification is not activated
+ * yet, window magnification will be enabled.
* @param scale the target scale, or {@link Float#NaN} to leave unchanged
* @param centerX the screen-relative X coordinate around which to center for magnification,
* or {@link Float#NaN} to leave unchanged.
@@ -1377,7 +1380,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
* between frame position Y and centerY,
* or {@link Float#NaN} to leave unchanged.
*/
- void enableWindowMagnificationInternal(float scale, float centerX, float centerY,
+ void updateWindowMagnificationInternal(float scale, float centerX, float centerY,
float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY) {
if (Float.compare(scale, 1.0f) < 0) {
deleteWindowMagnification();
@@ -1433,7 +1436,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return;
}
- enableWindowMagnificationInternal(scale, Float.NaN, Float.NaN);
+ updateWindowMagnificationInternal(scale, Float.NaN, Float.NaN);
mHandler.removeCallbacks(mUpdateStateDescriptionRunnable);
mHandler.postDelayed(mUpdateStateDescriptionRunnable, UPDATE_STATE_DESCRIPTION_DELAY_MS);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 96eb4b3a4c3f..475bb2c83b0e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -43,14 +43,14 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.HearingDeviceItemCallback;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.bluetooth.qsdialog.ActiveHearingDeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.AvailableHearingDeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.ConnectedDeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.SavedHearingDeviceItemFactory;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.qs.tiles.dialog.bluetooth.ActiveHearingDeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.AvailableHearingDeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.ConnectedDeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.SavedHearingDeviceItemFactory;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
index 695d04f5df9d..737805b4d62f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
@@ -27,7 +27,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
import com.android.systemui.res.R;
import kotlin.Pair;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 3a45db17b64c..61d1c713fb77 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -323,7 +323,13 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
overlayParams = updatedOverlayParams
sensorBounds = updatedOverlayParams.sensorBounds
getTouchOverlay()?.let {
- windowManager.updateViewLayout(it, coreLayoutParams.updateDimensions(null))
+ if (addViewRunnable != null) {
+ // Only updateViewLayout if there's no pending view to add to WM.
+ // If there is a pending view, that means the view hasn't been added yet so there's
+ // no need to update any layouts. Instead the correct params will be used when the
+ // view is eventually added.
+ windowManager.updateViewLayout(it, coreLayoutParams.updateDimensions(null))
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index 7d6721903c37..591da4096956 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -18,7 +18,6 @@ package com.android.systemui.biometrics.domain.interactor
import android.content.Context
import android.content.res.Configuration
-import android.view.Display
import com.android.systemui.biometrics.data.repository.DisplayStateRepository
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -28,7 +27,6 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.unfold.updates.FoldProvider
-import com.android.systemui.util.kotlin.sample
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -36,8 +34,6 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Aggregates display state information. */
@@ -129,16 +125,7 @@ constructor(
screenSizeFoldProvider.onConfigurationChange(newConfig)
}
- private val defaultDisplay =
- displayRepository.displays.map { displays ->
- displays.firstOrNull { it.displayId == Display.DEFAULT_DISPLAY }
- }
-
- override val isDefaultDisplayOff =
- displayRepository.displayChangeEvent
- .filter { it == Display.DEFAULT_DISPLAY }
- .sample(defaultDisplay)
- .map { it?.state == Display.STATE_OFF }
+ override val isDefaultDisplayOff = displayRepository.defaultDisplayOff
override val isLargeScreen: Flow<Boolean> = displayStateRepository.isLargeScreen
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
index 83b3380ae6be..1eef91debab3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
@@ -27,7 +27,8 @@ import com.android.systemui.biometrics.udfps.TouchProcessorResult.ProcessedTouch
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
-private val SUPPORTED_ROTATIONS = setOf(Surface.ROTATION_90, Surface.ROTATION_270)
+private val SUPPORTED_ROTATIONS =
+ setOf(Surface.ROTATION_90, Surface.ROTATION_270, Surface.ROTATION_180)
/**
* TODO(b/259140693): Consider using an object pool of TouchProcessorResult to avoid allocations.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 76d46ed9889f..072fe47df42b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -463,6 +463,23 @@ object BiometricViewBinder {
}
}
}
+
+ // Retry and confirmation when finger on sensor
+ launch {
+ combine(
+ viewModel.canTryAgainNow,
+ viewModel.hasFingerOnSensor,
+ viewModel.isPendingConfirmation,
+ ::Triple
+ )
+ .collect { (canRetry, fingerAcquired, pendingConfirmation) ->
+ if (canRetry && fingerAcquired) {
+ legacyCallback.onButtonTryAgain()
+ } else if (pendingConfirmation && fingerAcquired) {
+ viewModel.confirmAuthenticated()
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 21ebff4d0b71..4e9acbd25b62 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -22,6 +22,7 @@ import android.content.pm.PackageManager
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
+import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.Flags.customBiometricPrompt
import android.hardware.biometrics.PromptContentView
@@ -32,6 +33,7 @@ import com.android.systemui.Flags.bpTalkback
import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
@@ -40,6 +42,7 @@ import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.Job
@@ -66,6 +69,7 @@ constructor(
promptSelectorInteractor: PromptSelectorInteractor,
@Application private val context: Context,
private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
+ private val biometricStatusInteractor: BiometricStatusInteractor,
private val udfpsUtils: UdfpsUtils
) {
/** The set of modalities available for this prompt */
@@ -185,6 +189,24 @@ constructor(
/** Fingerprint sensor state. */
val fingerprintStartMode: Flow<FingerprintStartMode> = _fingerprintStartMode.asStateFlow()
+ /** Whether a finger has been acquired by the sensor */
+ // TODO(b/331948073): Add support for detecting SFPS finger without authentication running
+ val hasFingerBeenAcquired: Flow<Boolean> =
+ combine(biometricStatusInteractor.fingerprintAcquiredStatus, modalities) {
+ status,
+ modalities ->
+ modalities.hasSfps &&
+ status is AcquiredFingerprintAuthenticationStatus &&
+ status.acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ }
+ .distinctUntilChanged()
+
+ /** Whether there is currently a finger on the sensor */
+ val hasFingerOnSensor: Flow<Boolean> =
+ combine(hasFingerBeenAcquired, _isOverlayTouched) { hasFingerBeenAcquired, overlayTouched ->
+ hasFingerBeenAcquired || overlayTouched
+ }
+
private val _forceLargeSize = MutableStateFlow(false)
private val _forceMediumSize = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractor.kt
index 59fc81c82df0..f86cad5b9c59 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractor.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt
index 9ee582a77862..81fe2a5a5f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt
@@ -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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.util.Log
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
index 9c63a30dfc1c..94d7af74f1dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
@@ -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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothAdapter.STATE_OFF
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index a8d9e781228b..c7d171d5b804 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.os.Bundle
import android.view.LayoutInflater
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
index 5d18dc1d453a..c30aea07e959 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
@@ -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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.DEBUG
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt
index ea51beecc2c1..6e51915797cc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import com.android.settingslib.bluetooth.CachedBluetoothDevice
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index cd52e0dcca4a..add1647143d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index fd624d2f1ba1..e65b65710f94 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.content.Intent
import android.content.SharedPreferences
@@ -31,15 +31,15 @@ import com.android.internal.logging.UiEventLogger
import com.android.systemui.Prefs
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.MAX_DEVICE_ITEM_ENTRY
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.MAX_DEVICE_ITEM_ENTRY
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index 1c621b87533d..dc5efefdfb16 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -30,7 +30,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.graphics.drawable.Drawable
import com.android.settingslib.bluetooth.CachedBluetoothDevice
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 56ba07941e4d..f04ba75ca3ef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothDevice
import android.content.Context
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index fce25ec68190..4e28cafb5004 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index aeb564d53195..02a40d93ab65 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.domain.interactor
+import com.android.compose.animation.scene.SceneKey
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
@@ -26,6 +27,8 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
@@ -47,6 +50,7 @@ constructor(
private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
private val falsingInteractor: FalsingInteractor,
private val powerInteractor: PowerInteractor,
+ sceneInteractor: SceneInteractor,
) {
private val _onIncorrectBouncerInput = MutableSharedFlow<Unit>()
val onIncorrectBouncerInput: SharedFlow<Unit> = _onIncorrectBouncerInput
@@ -80,6 +84,10 @@ constructor(
}
.map {}
+ /** The scene to show when bouncer is dismissed. */
+ val dismissDestination: Flow<SceneKey> =
+ sceneInteractor.previousScene.map { it ?: Scenes.Lockscreen }
+
/** Notifies that the user has places down a pointer, not necessarily dragging just yet. */
fun onDown() {
falsingInteractor.avoidGesture()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 5c07cc57c620..7c41b75d7105 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -21,6 +21,12 @@ import android.app.admin.DevicePolicyResources
import android.content.Context
import android.graphics.Bitmap
import androidx.core.graphics.drawable.toBitmap
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
@@ -35,6 +41,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
@@ -82,6 +89,15 @@ class BouncerViewModel(
initialValue = null,
)
+ val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ bouncerInteractor.dismissDestination
+ .map(::destinationSceneMap)
+ .stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(),
+ initialValue = destinationSceneMap(Scenes.Lockscreen),
+ )
+
val message: BouncerMessageViewModel = bouncerMessageViewModel
val userSwitcherDropdown: StateFlow<List<UserSwitcherDropdownItemViewModel>> =
@@ -310,8 +326,7 @@ class BouncerViewModel(
{ message },
failedAttempts,
remainingAttempts,
- )
- ?: message
+ ) ?: message
} else {
message
}
@@ -328,8 +343,7 @@ class BouncerViewModel(
.KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE,
{ message },
failedAttempts,
- )
- ?: message
+ ) ?: message
} else {
message
}
@@ -357,6 +371,12 @@ class BouncerViewModel(
}
}
+ private fun destinationSceneMap(prevScene: SceneKey) =
+ mapOf(
+ Back to UserActionResult(prevScene),
+ Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
+ )
+
data class DialogViewModel(
val text: String,
@@ -400,13 +420,13 @@ object BouncerViewModelModule {
simBouncerInteractor = simBouncerInteractor,
authenticationInteractor = authenticationInteractor,
selectedUserInteractor = selectedUserInteractor,
+ devicePolicyManager = devicePolicyManager,
+ bouncerMessageViewModel = bouncerMessageViewModel,
flags = flags,
selectedUser = userSwitcherViewModel.selectedUser,
users = userSwitcherViewModel.users,
userSwitcherMenu = userSwitcherViewModel.menu,
actionButton = actionButtonInteractor.actionButton,
- devicePolicyManager = devicePolicyManager,
- bouncerMessageViewModel = bouncerMessageViewModel,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 357eca37ba37..d2caefd3b552 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -398,6 +398,7 @@ public class BrightLineFalsingManager implements FalsingManager {
|| mAccessibilityManager.isTouchExplorationEnabled()
|| mDataProvider.isA11yAction()
|| mDataProvider.isFromTrackpad()
+ || mDataProvider.isFromKeyboard()
|| (mFeatureFlags.isEnabled(Flags.FALSING_OFF_FOR_UNFOLDED)
&& mDataProvider.isUnfolded());
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index a79a654aedc2..76b228d4726a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -16,6 +16,7 @@
package com.android.systemui.classifier;
+import android.view.KeyEvent;
import android.view.MotionEvent;
/**
@@ -50,6 +51,14 @@ public interface FalsingCollector {
void onBouncerHidden();
/**
+ * Call this to record a KeyEvent in the {@link com.android.systemui.plugins.FalsingManager}.
+ *
+ * This may decide to only collect certain KeyEvents and ignore others. Do not assume all
+ * KeyEvents are collected.
+ */
+ void onKeyEvent(KeyEvent ev);
+
+ /**
* Call this to record a MotionEvent in the {@link com.android.systemui.plugins.FalsingManager}.
*
* Be sure to call {@link #onMotionEventComplete()} after the rest of SystemUI is done with the
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index d6b9a119e31c..dcd419512498 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -16,6 +16,7 @@
package com.android.systemui.classifier;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import javax.inject.Inject;
@@ -23,6 +24,8 @@ import javax.inject.Inject;
/** */
public class FalsingCollectorFake implements FalsingCollector {
+ public KeyEvent lastKeyEvent = null;
+
@Override
public void init() {
}
@@ -70,6 +73,11 @@ public class FalsingCollectorFake implements FalsingCollector {
}
@Override
+ public void onKeyEvent(KeyEvent ev) {
+ lastKeyEvent = ev;
+ }
+
+ @Override
public void onTouchEvent(MotionEvent ev) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 4f4f3d0324b3..beaa170943fd 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -21,6 +21,7 @@ import static com.android.systemui.dock.DockManager.DockEventListener;
import android.hardware.SensorManager;
import android.hardware.biometrics.BiometricSourceType;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.annotation.VisibleForTesting;
@@ -49,7 +50,10 @@ import com.android.systemui.util.time.SystemClock;
import dagger.Lazy;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
import javax.inject.Inject;
@@ -61,6 +65,14 @@ class FalsingCollectorImpl implements FalsingCollector {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final long GESTURE_PROCESSING_DELAY_MS = 100;
+ private final Set<Integer> mAcceptedKeycodes = new HashSet<>(Arrays.asList(
+ KeyEvent.KEYCODE_ENTER,
+ KeyEvent.KEYCODE_ESCAPE,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_SHIFT_RIGHT,
+ KeyEvent.KEYCODE_SPACE
+ ));
+
private final FalsingDataProvider mFalsingDataProvider;
private final FalsingManager mFalsingManager;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -279,6 +291,14 @@ class FalsingCollectorImpl implements FalsingCollector {
}
@Override
+ public void onKeyEvent(KeyEvent ev) {
+ // Only collect if it is an ACTION_UP action and is allow-listed
+ if (ev.getAction() == KeyEvent.ACTION_UP && mAcceptedKeycodes.contains(ev.getKeyCode())) {
+ mFalsingDataProvider.onKeyEvent(ev);
+ }
+ }
+
+ @Override
public void onTouchEvent(MotionEvent ev) {
logDebug("REAL: onTouchEvent(" + ev.getActionMasked() + ")");
if (!mKeyguardStateController.isShowing()) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
index c5d8c795853d..b289fa49d06c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
@@ -16,6 +16,7 @@
package com.android.systemui.classifier
+import android.view.KeyEvent
import android.view.MotionEvent
import com.android.systemui.classifier.FalsingCollectorImpl.logDebug
import com.android.systemui.dagger.SysUISingleton
@@ -59,6 +60,10 @@ class FalsingCollectorNoOp @Inject constructor() : FalsingCollector {
logDebug("NOOP: onBouncerHidden")
}
+ override fun onKeyEvent(ev: KeyEvent) {
+ logDebug("NOOP: onKeyEvent(${ev.action}")
+ }
+
override fun onTouchEvent(ev: MotionEvent) {
logDebug("NOOP: onTouchEvent(${ev.actionMasked})")
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 809d5b289cab..15017011134b 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -20,6 +20,7 @@ import static com.android.systemui.classifier.FalsingModule.IS_FOLDABLE_DEVICE;
import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.util.DisplayMetrics;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
@@ -41,6 +42,7 @@ import javax.inject.Named;
public class FalsingDataProvider {
private static final long MOTION_EVENT_AGE_MS = 1000;
+ private static final long KEY_EVENT_AGE_MS = 500;
private static final long DROP_EVENT_THRESHOLD_MS = 50;
private static final float THREE_HUNDRED_SIXTY_DEG = (float) (2 * Math.PI);
@@ -56,8 +58,10 @@ public class FalsingDataProvider {
private final List<MotionEventListener> mMotionEventListeners = new ArrayList<>();
private final List<GestureFinalizedListener> mGestureFinalizedListeners = new ArrayList<>();
- private TimeLimitedMotionEventBuffer mRecentMotionEvents =
- new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
+ private TimeLimitedInputEventBuffer<MotionEvent> mRecentMotionEvents =
+ new TimeLimitedInputEventBuffer<>(MOTION_EVENT_AGE_MS);
+ private final TimeLimitedInputEventBuffer<KeyEvent> mRecentKeyEvents =
+ new TimeLimitedInputEventBuffer<>(KEY_EVENT_AGE_MS);
private List<MotionEvent> mPriorMotionEvents = new ArrayList<>();
private boolean mDirty = true;
@@ -89,6 +93,10 @@ public class FalsingDataProvider {
FalsingClassifier.logInfo("width, height: " + getWidthPixels() + ", " + getHeightPixels());
}
+ void onKeyEvent(KeyEvent keyEvent) {
+ mRecentKeyEvents.add(keyEvent);
+ }
+
void onMotionEvent(MotionEvent motionEvent) {
List<MotionEvent> motionEvents = unpackMotionEvent(motionEvent);
FalsingClassifier.logVerbose("Unpacked into: " + motionEvents.size());
@@ -109,6 +117,10 @@ public class FalsingDataProvider {
// previous ACTION_MOVE event and when it happens, it makes some classifiers fail.
mDropLastEvent = shouldDropEvent(motionEvent);
+ if (!motionEvents.isEmpty() && !mRecentKeyEvents.isEmpty()) {
+ recycleAndClearRecentKeyEvents();
+ }
+
mRecentMotionEvents.addAll(motionEvents);
FalsingClassifier.logVerbose("Size: " + mRecentMotionEvents.size());
@@ -141,7 +153,7 @@ public class FalsingDataProvider {
mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getEventTime()));
mPriorMotionEvents = mRecentMotionEvents;
- mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
+ mRecentMotionEvents = new TimeLimitedInputEventBuffer<>(MOTION_EVENT_AGE_MS);
}
mDropLastEvent = false;
mA11YAction = false;
@@ -261,6 +273,13 @@ public class FalsingDataProvider {
return mLastMotionEvent.getY() < mFirstRecentMotionEvent.getY();
}
+ /**
+ * If it's a specific set of keys as collected from {@link FalsingCollector}
+ */
+ public boolean isFromKeyboard() {
+ return !mRecentKeyEvents.isEmpty();
+ }
+
public boolean isFromTrackpad() {
if (mRecentMotionEvents.isEmpty()) {
return false;
@@ -318,6 +337,14 @@ public class FalsingDataProvider {
}
}
+ private void recycleAndClearRecentKeyEvents() {
+ for (KeyEvent ev : mRecentKeyEvents) {
+ ev.recycle();
+ }
+
+ mRecentKeyEvents.clear();
+ }
+
private List<MotionEvent> unpackMotionEvent(MotionEvent motionEvent) {
List<MotionEvent> motionEvents = new ArrayList<>();
List<PointerProperties> pointerPropertiesList = new ArrayList<>();
@@ -416,6 +443,8 @@ public class FalsingDataProvider {
mRecentMotionEvents.clear();
+ recycleAndClearRecentKeyEvents();
+
mDirty = true;
mSessionListeners.forEach(SessionListener::onSessionEnded);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedInputEventBuffer.java
index 51aede79b581..7627ad19f650 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedInputEventBuffer.java
@@ -16,7 +16,7 @@
package com.android.systemui.classifier;
-import android.view.MotionEvent;
+import android.view.InputEvent;
import java.util.ArrayList;
import java.util.Collection;
@@ -25,31 +25,31 @@ import java.util.List;
import java.util.ListIterator;
/**
- * Maintains an ordered list of the last N milliseconds of MotionEvents.
+ * Maintains an ordered list of the last N milliseconds of InputEvents.
*
* This class is simply a convenience class designed to look like a simple list, but that
- * automatically discards old MotionEvents. It functions much like a queue - first in first out -
+ * automatically discards old InputEvents. It functions much like a queue - first in first out -
* but does not have a fixed size like a circular buffer.
*/
-public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
+public class TimeLimitedInputEventBuffer<T extends InputEvent> implements List<T> {
- private final List<MotionEvent> mMotionEvents;
+ private final List<T> mInputEvents;
private final long mMaxAgeMs;
- public TimeLimitedMotionEventBuffer(long maxAgeMs) {
+ public TimeLimitedInputEventBuffer(long maxAgeMs) {
super();
mMaxAgeMs = maxAgeMs;
- mMotionEvents = new ArrayList<>();
+ mInputEvents = new ArrayList<>();
}
private void ejectOldEvents() {
- if (mMotionEvents.isEmpty()) {
+ if (mInputEvents.isEmpty()) {
return;
}
- Iterator<MotionEvent> iter = listIterator();
- long mostRecentMs = mMotionEvents.get(mMotionEvents.size() - 1).getEventTime();
+ Iterator<T> iter = listIterator();
+ long mostRecentMs = mInputEvents.get(mInputEvents.size() - 1).getEventTime();
while (iter.hasNext()) {
- MotionEvent ev = iter.next();
+ T ev = iter.next();
if (mostRecentMs - ev.getEventTime() > mMaxAgeMs) {
iter.remove();
ev.recycle();
@@ -58,140 +58,140 @@ public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
}
@Override
- public void add(int index, MotionEvent element) {
+ public void add(int index, T element) {
throw new UnsupportedOperationException();
}
@Override
- public MotionEvent remove(int index) {
- return mMotionEvents.remove(index);
+ public T remove(int index) {
+ return mInputEvents.remove(index);
}
@Override
public int indexOf(Object o) {
- return mMotionEvents.indexOf(o);
+ return mInputEvents.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
- return mMotionEvents.lastIndexOf(o);
+ return mInputEvents.lastIndexOf(o);
}
@Override
public int size() {
- return mMotionEvents.size();
+ return mInputEvents.size();
}
@Override
public boolean isEmpty() {
- return mMotionEvents.isEmpty();
+ return mInputEvents.isEmpty();
}
@Override
public boolean contains(Object o) {
- return mMotionEvents.contains(o);
+ return mInputEvents.contains(o);
}
@Override
- public Iterator<MotionEvent> iterator() {
- return mMotionEvents.iterator();
+ public Iterator<T> iterator() {
+ return mInputEvents.iterator();
}
@Override
public Object[] toArray() {
- return mMotionEvents.toArray();
+ return mInputEvents.toArray();
}
@Override
- public <T> T[] toArray(T[] a) {
- return mMotionEvents.toArray(a);
+ public <T2> T2[] toArray(T2[] a) {
+ return mInputEvents.toArray(a);
}
@Override
- public boolean add(MotionEvent element) {
- boolean result = mMotionEvents.add(element);
+ public boolean add(T element) {
+ boolean result = mInputEvents.add(element);
ejectOldEvents();
return result;
}
@Override
public boolean remove(Object o) {
- return mMotionEvents.remove(o);
+ return mInputEvents.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
- return mMotionEvents.containsAll(c);
+ return mInputEvents.containsAll(c);
}
@Override
- public boolean addAll(Collection<? extends MotionEvent> collection) {
- boolean result = mMotionEvents.addAll(collection);
+ public boolean addAll(Collection<? extends T> collection) {
+ boolean result = mInputEvents.addAll(collection);
ejectOldEvents();
return result;
}
@Override
- public boolean addAll(int index, Collection<? extends MotionEvent> elements) {
+ public boolean addAll(int index, Collection<? extends T> elements) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(Collection<?> c) {
- return mMotionEvents.removeAll(c);
+ return mInputEvents.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
- return mMotionEvents.retainAll(c);
+ return mInputEvents.retainAll(c);
}
@Override
public void clear() {
- mMotionEvents.clear();
+ mInputEvents.clear();
}
@Override
public boolean equals(Object o) {
- return mMotionEvents.equals(o);
+ return mInputEvents.equals(o);
}
@Override
public int hashCode() {
- return mMotionEvents.hashCode();
+ return mInputEvents.hashCode();
}
@Override
- public MotionEvent get(int index) {
- return mMotionEvents.get(index);
+ public T get(int index) {
+ return mInputEvents.get(index);
}
@Override
- public MotionEvent set(int index, MotionEvent element) {
+ public T set(int index, T element) {
throw new UnsupportedOperationException();
}
@Override
- public ListIterator<MotionEvent> listIterator() {
+ public ListIterator<T> listIterator() {
return new Iter(0);
}
@Override
- public ListIterator<MotionEvent> listIterator(int index) {
+ public ListIterator<T> listIterator(int index) {
return new Iter(index);
}
@Override
- public List<MotionEvent> subList(int fromIndex, int toIndex) {
- return mMotionEvents.subList(fromIndex, toIndex);
+ public List<T> subList(int fromIndex, int toIndex) {
+ return mInputEvents.subList(fromIndex, toIndex);
}
- class Iter implements ListIterator<MotionEvent> {
+ class Iter implements ListIterator<T> {
- private final ListIterator<MotionEvent> mIterator;
+ private final ListIterator<T> mIterator;
Iter(int index) {
- this.mIterator = mMotionEvents.listIterator(index);
+ this.mIterator = mInputEvents.listIterator(index);
}
@Override
@@ -200,7 +200,7 @@ public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
}
@Override
- public MotionEvent next() {
+ public T next() {
return mIterator.next();
}
@@ -210,7 +210,7 @@ public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
}
@Override
- public MotionEvent previous() {
+ public T previous() {
return mIterator.previous();
}
@@ -230,12 +230,12 @@ public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
}
@Override
- public void set(MotionEvent motionEvent) {
+ public void set(T inputEvent) {
throw new UnsupportedOperationException();
}
@Override
- public void add(MotionEvent element) {
+ public void add(T element) {
throw new UnsupportedOperationException();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
index 7dd883b89db7..f7ea25c8c0ca 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
@@ -46,8 +46,25 @@ inline fun <reified T : View> View.getNearestParent(): T? {
}
/** Adds a [View.OnLayoutChangeListener] and provides a [DisposableHandle] for teardown. */
-fun View.onLayoutChanged(onLayoutChanged: (v: View) -> Unit): DisposableHandle {
- val listener = View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> onLayoutChanged(v) }
+fun View.onLayoutChanged(onLayoutChanged: (v: View) -> Unit): DisposableHandle =
+ onLayoutChanged { v, _, _, _, _, _, _, _, _ ->
+ onLayoutChanged(v)
+ }
+
+/** Adds the [View.OnLayoutChangeListener] and provides a [DisposableHandle] for teardown. */
+fun View.onLayoutChanged(listener: View.OnLayoutChangeListener): DisposableHandle {
addOnLayoutChangeListener(listener)
return DisposableHandle { removeOnLayoutChangeListener(listener) }
}
+
+/** Adds a [View.OnApplyWindowInsetsListener] and provides a [DisposableHandle] for teardown. */
+fun View.onApplyWindowInsets(listener: View.OnApplyWindowInsetsListener): DisposableHandle {
+ setOnApplyWindowInsetsListener(listener)
+ return DisposableHandle { setOnApplyWindowInsetsListener(null) }
+}
+
+/** Adds a [View.OnTouchListener] and provides a [DisposableHandle] for teardown. */
+fun View.onTouchListener(listener: View.OnTouchListener): DisposableHandle {
+ setOnTouchListener(listener)
+ return DisposableHandle { setOnTouchListener(null) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index 4d328d6cb13f..5a174b9d2f80 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -21,6 +21,7 @@ import com.android.compose.animation.scene.SceneKey
import com.android.systemui.CoreStartable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -70,9 +71,9 @@ constructor(
keyguardTransitionInteractor.startedKeyguardTransitionStep
.mapLatest(::determineSceneAfterTransition)
.filterNotNull()
- // TODO(b/322787129): Also set a custom transition animation here to avoid the regular
- // slide-in animation when setting the scene programmatically
- .onEach { nextScene -> communalInteractor.changeScene(nextScene) }
+ .onEach { nextScene ->
+ communalInteractor.changeScene(nextScene, CommunalTransitionKeys.SimpleFade)
+ }
.launchIn(applicationScope)
// TODO(b/322787129): re-enable once custom animations are in place
@@ -143,7 +144,14 @@ constructor(
val docked = dockManager.isDocked
return when {
- docked && to == KeyguardState.LOCKSCREEN && from == KeyguardState.DREAMING -> {
+ to == KeyguardState.OCCLUDED -> {
+ // Hide communal when an activity is started on keyguard, to ensure the activity
+ // underneath the hub is shown.
+ CommunalScenes.Blank
+ }
+ to == KeyguardState.GLANCEABLE_HUB && from == KeyguardState.OCCLUDED -> {
+ // When transitioning to the hub from an occluded state, fade out the hub without
+ // doing any translation.
CommunalScenes.Communal
}
to == KeyguardState.GONE -> CommunalScenes.Blank
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index c724244816ea..9debe0e56083 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -57,6 +57,9 @@ interface CommunalSettingsRepository {
* Settings.
*/
fun getWidgetCategories(user: UserInfo): Flow<CommunalWidgetCategories>
+
+ /** Keyguard widgets enabled state by Device Policy Manager for the specified user. */
+ fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean>
}
@SysUISingleton
@@ -115,6 +118,16 @@ constructor(
}
.flowOn(bgDispatcher)
+ override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> =
+ broadcastDispatcher
+ .broadcastFlow(
+ filter =
+ IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ user = user.userHandle
+ )
+ .emitOnStart()
+ .map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }
+
private fun getEnabledByUser(user: UserInfo): Flow<Boolean> =
secureSettings
.observerFlow(userId = user.id, names = arrayOf(Settings.Secure.GLANCEABLE_HUB_ENABLED))
@@ -128,16 +141,6 @@ constructor(
) == 1
}
- private fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> =
- broadcastDispatcher
- .broadcastFlow(
- filter =
- IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- user = user.userHandle
- )
- .emitOnStart()
- .map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }
-
companion object {
const val GLANCEABLE_HUB_CONTENT_SETTING = "glanceable_hub_content_setting"
private const val ENABLED_SETTING_DEFAULT = 1
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 246d5d92f8b0..373e1c9daa7b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -44,6 +44,8 @@ import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.retrieveIsDocked
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
@@ -97,12 +99,13 @@ constructor(
mediaRepository: CommunalMediaRepository,
smartspaceRepository: SmartspaceRepository,
keyguardInteractor: KeyguardInteractor,
- communalSettingsInteractor: CommunalSettingsInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
private val userTracker: UserTracker,
private val activityStarter: ActivityStarter,
private val userManager: UserManager,
+ private val dockManager: DockManager,
sceneInteractor: SceneInteractor,
sceneContainerFlags: SceneContainerFlags,
@CommunalLog logBuffer: LogBuffer,
@@ -123,7 +126,7 @@ constructor(
and(
communalSettingsInteractor.isCommunalEnabled,
not(keyguardInteractor.isEncryptedOrLockdown),
- or(keyguardInteractor.isKeyguardVisible, keyguardInteractor.isDreaming)
+ or(keyguardInteractor.isKeyguardShowing, keyguardInteractor.isDreaming)
)
.distinctUntilChanged()
.onEach { available ->
@@ -143,6 +146,9 @@ constructor(
replay = 1,
)
+ /** Whether to show communal by default */
+ val showByDefault: Flow<Boolean> = and(isCommunalAvailable, dockManager.retrieveIsDocked())
+
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
*
@@ -352,7 +358,14 @@ constructor(
/** A list of widget content to be displayed in the communal hub. */
val widgetContent: Flow<List<WidgetContent>> =
combine(
- widgetRepository.communalWidgets.map { filterWidgetsByExistingUsers(it) },
+ widgetRepository.communalWidgets
+ .map { filterWidgetsByExistingUsers(it) }
+ .combine(communalSettingsInteractor.allowedByDevicePolicyForWorkProfile) {
+ // exclude widgets under work profile if not allowed by device policy
+ widgets,
+ allowedForWorkProfile ->
+ filterWidgetsAllowedByDevicePolicy(widgets, allowedForWorkProfile)
+ },
communalSettingsInteractor.communalWidgetCategories,
updateOnWorkProfileBroadcastReceived,
) { widgets, allowedCategories, _ ->
@@ -374,6 +387,19 @@ constructor(
}
}
+ /** Filter widgets based on whether their associated profile is allowed by device policy. */
+ private fun filterWidgetsAllowedByDevicePolicy(
+ list: List<CommunalWidgetContentModel>,
+ allowedByDevicePolicyForWorkProfile: Boolean
+ ): List<CommunalWidgetContentModel> =
+ if (allowedByDevicePolicyForWorkProfile) {
+ list
+ } else {
+ // Get associated work profile for the currently selected user.
+ val workProfile = userTracker.userProfiles.find { it.isManagedProfile }
+ list.filter { it.providerInfo.profile.identifier != workProfile?.id }
+ }
+
/** A flow of available smartspace targets. Currently only showing timers. */
private val smartspaceTargets: Flow<List<SmartspaceTarget>> =
if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index 20f60b79c784..f9de60984e2d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.communal.domain.interactor
+import android.content.pm.UserInfo
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.communal.data.model.CommunalEnabledState
import com.android.systemui.communal.data.model.CommunalWidgetCategories
import com.android.systemui.communal.data.repository.CommunalSettingsRepository
@@ -24,13 +26,18 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.dagger.CommunalTableLog
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.settings.UserTracker
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -40,8 +47,10 @@ class CommunalSettingsInteractor
@Inject
constructor(
@Background private val bgScope: CoroutineScope,
+ @Background private val bgExecutor: Executor,
private val repository: CommunalSettingsRepository,
userInteractor: SelectedUserInteractor,
+ private val userTracker: UserTracker,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
/** Whether or not communal is enabled for the currently selected user. */
@@ -68,4 +77,33 @@ constructor(
started = SharingStarted.Eagerly,
initialValue = CommunalWidgetCategories().categories
)
+
+ private val workProfileUserInfoCallbackFlow: Flow<UserInfo?> = conflatedCallbackFlow {
+ fun send(profiles: List<UserInfo>) {
+ trySend(profiles.find { it.isManagedProfile })
+ }
+
+ val callback =
+ object : UserTracker.Callback {
+ override fun onProfilesChanged(profiles: List<UserInfo>) {
+ send(profiles)
+ }
+ }
+ userTracker.addCallback(callback, bgExecutor)
+ send(userTracker.userProfiles)
+
+ awaitClose { userTracker.removeCallback(callback) }
+ }
+
+ /** Whether or not keyguard widgets are allowed for work profile by device policy manager. */
+ val allowedByDevicePolicyForWorkProfile: StateFlow<Boolean> =
+ workProfileUserInfoCallbackFlow
+ .flatMapLatest { workProfile ->
+ workProfile?.let { repository.getAllowedByDevicePolicy(it) } ?: flowOf(false)
+ }
+ .stateIn(
+ scope = bgScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
new file mode 100644
index 000000000000..a3c61a413639
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.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.communal.shared.model
+
+import com.android.compose.animation.scene.TransitionKey
+
+/**
+ * Defines all known named transitions for [CommunalScenes].
+ *
+ * These transitions can be referenced by key when changing scenes programmatically.
+ */
+object CommunalTransitionKeys {
+ /** Fades the glanceable hub without any translation */
+ val SimpleFade = TransitionKey("SimpleFade")
+}
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 96e4b341cb6d..bdf4e721a551 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
@@ -16,7 +16,11 @@
package com.android.systemui.communal.ui.viewmodel
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
@@ -25,6 +29,7 @@ import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.merge
/** View model for transitions related to the communal hub. */
@@ -37,6 +42,8 @@ constructor(
lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
glanceableHubToDreamTransitionViewModel: GlanceableHubToDreamingTransitionViewModel,
+ communalInteractor: CommunalInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
/**
* Whether UMO location should be on communal. This flow is responsive to transitions so that a
@@ -51,4 +58,14 @@ constructor(
glanceableHubToDreamTransitionViewModel.showUmo,
)
.distinctUntilChanged()
+
+ /** Whether to show communal by default */
+ val showByDefault: Flow<Boolean> = communalInteractor.showByDefault
+
+ val transitionFromOccludedEnded =
+ keyguardTransitionInteractor.transitionStepsFromState(KeyguardState.OCCLUDED).filter { step
+ ->
+ step.transitionState == TransitionState.FINISHED ||
+ step.transitionState == TransitionState.CANCELED
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index baae986c494d..1eba0662a43d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -40,7 +40,6 @@ import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationSta
import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
@@ -51,6 +50,7 @@ import com.android.systemui.keyguard.data.repository.TrustRepository
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.StatusBarState
import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
@@ -313,10 +313,16 @@ constructor(
// or device starts going to sleep.
merge(
powerInteractor.isAsleep,
- if (KeyguardWmStateRefactor.isEnabled) {
- keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
- } else {
- keyguardRepository.keyguardDoneAnimationsFinished.map { true }
+ combine(
+ keyguardTransitionInteractor.isFinishedInState(KeyguardState.GONE),
+ keyguardInteractor.statusBarState,
+ ) { isFinishedInGoneState, statusBarState ->
+ // When the user is dragging the primary bouncer in (up) by manually scrolling
+ // up on the lockscreen, the device won't be irreversibly transitioned to GONE
+ // until the statusBarState updates to SHADE, so we check that here.
+ // Else, we could reset the face auth state too early and end up in a strange
+ // state.
+ isFinishedInGoneState && statusBarState == StatusBarState.SHADE
},
userRepository.selectedUser.map {
it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 1230156953ee..4ac0c5683d06 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -42,6 +42,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -67,6 +68,9 @@ interface DisplayRepository {
*/
val pendingDisplay: Flow<PendingDisplay?>
+ /** Whether the default display is currently off. */
+ val defaultDisplayOff: Flow<Boolean>
+
/** Represents a connected display that has not been enabled yet. */
interface PendingDisplay {
/** Id of the pending display. */
@@ -290,6 +294,11 @@ constructor(
}
.debugLog("pendingDisplay")
+ override val defaultDisplayOff: Flow<Boolean> =
+ displays
+ .map { displays -> displays.firstOrNull { it.displayId == Display.DEFAULT_DISPLAY } }
+ .map { it?.state == Display.STATE_OFF }
+
private fun <T> Flow<T>.debugLog(flowName: String): Flow<T> {
return if (DEBUG) {
traceEach(flowName, logcat = true, traceEmissionCount = true)
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 1a855d735a02..95012a2643a5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -54,8 +54,6 @@ public class DozeUi implements DozeMachine.Part {
private final DozeParameters mDozeParameters;
private final DozeLog mDozeLog;
private final DelayableExecutor mBgExecutor;
-
- private Runnable mCancelRunnable = null;
private long mLastTimeTickElapsed = 0;
// If time tick is scheduled and there's not a pending runnable to cancel:
private volatile boolean mTimeTickScheduled;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index 037c23b579c3..ac03463da545 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -21,6 +21,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
@@ -28,6 +29,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransition
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -49,7 +51,8 @@ constructor(
private val communalInteractor: CommunalInteractor,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val userTracker: UserTracker,
-) {
+ dumpManager: DumpManager,
+) : FlowDumperImpl(dumpManager) {
fun startTransitionFromDream() {
val showGlanceableHub =
@@ -83,6 +86,7 @@ constructor(
toGlanceableHubTransitionViewModel.dreamAlpha,
)
.distinctUntilChanged()
+ .dumpWhileCollecting("dreamAlpha")
val dreamOverlayAlpha: Flow<Float> =
merge(
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 640534cc9d34..612ae6c875f4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -153,11 +153,6 @@ object Flags {
// TODO(b/267722622): Tracking Bug
@JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag("wallpaper_picker_ui_for_aiwp")
- /** Whether to use a new data source for intents to run on keyguard dismissal. */
- // TODO(b/275069969): Tracking bug.
- @JvmField
- val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag("refactor_keyguard_dismiss_intent")
-
/** Whether to allow long-press on the lock screen to directly open wallpaper picker. */
// TODO(b/277220285): Tracking bug.
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index e6e6ff6dcadb..c32c226441fe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -41,7 +41,6 @@ import com.android.keyguard.dagger.KeyguardStatusViewComponent
import com.android.systemui.CoreStartable
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
@@ -73,7 +72,6 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -110,7 +108,6 @@ constructor(
private val keyguardBlueprintViewBinder: KeyguardBlueprintViewBinder,
private val clockInteractor: KeyguardClockInteractor,
private val keyguardViewMediator: KeyguardViewMediator,
- @Main private val mainImmediateDispatcher: CoroutineDispatcher,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -214,7 +211,6 @@ constructor(
vibratorHelper,
falsingManager,
keyguardViewMediator,
- mainImmediateDispatcher,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 654610e8cae8..2a9dad0162c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -137,6 +137,7 @@ import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.TransitionAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
@@ -584,7 +585,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private CentralSurfaces mCentralSurfaces;
- private IRemoteAnimationFinishedCallback mUnoccludeFromDreamFinishedCallback;
+ private IRemoteAnimationFinishedCallback mUnoccludeFinishedCallback;
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
new DeviceConfig.OnPropertiesChangedListener() {
@@ -1234,10 +1235,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mUnoccludeAnimator.cancel();
}
- if (isDream) {
+ if (isDream || mShowCommunalByDefault) {
initAlphaForAnimationTargets(wallpapers);
- mDreamViewModel.get().startTransitionFromDream();
- mUnoccludeFromDreamFinishedCallback = finishedCallback;
+ if (isDream) {
+ mDreamViewModel.get().startTransitionFromDream();
+ }
+ mUnoccludeFinishedCallback = finishedCallback;
return;
}
@@ -1304,7 +1307,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private Consumer<Float> getRemoteSurfaceAlphaApplier() {
return (Float alpha) -> {
- if (mRemoteAnimationTarget == null) return;
+ if (mRemoteAnimationTarget == null) {
+ Log.e(TAG, "Attempting to set alpha on null animation target");
+ return;
+ }
final View localView = mKeyguardViewControllerLazy.get().getViewRootImpl().getView();
final SyncRtSurfaceTransactionApplier applier =
new SyncRtSurfaceTransactionApplier(localView);
@@ -1319,10 +1325,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private Consumer<TransitionStep> getFinishedCallbackConsumer() {
return (TransitionStep step) -> {
- if (mUnoccludeFromDreamFinishedCallback == null) return;
+ if (mUnoccludeFinishedCallback == null) return;
try {
- mUnoccludeFromDreamFinishedCallback.onAnimationFinished();
- mUnoccludeFromDreamFinishedCallback = null;
+ mUnoccludeFinishedCallback.onAnimationFinished();
+ mUnoccludeFinishedCallback = null;
} catch (RemoteException e) {
Log.e(TAG, "Wasn't able to callback", e);
}
@@ -1365,7 +1371,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private final SessionTracker mSessionTracker;
private final CoroutineDispatcher mMainDispatcher;
private final Lazy<DreamViewModel> mDreamViewModel;
+ private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
+ private Boolean mShowCommunalByDefault;
private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
@@ -1414,6 +1422,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
SystemClock systemClock,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamViewModel> dreamViewModel,
+ Lazy<CommunalTransitionViewModel> communalTransitionViewModel,
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
@@ -1485,6 +1494,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mSessionTracker = sessionTracker;
mDreamViewModel = dreamViewModel;
+ mCommunalTransitionViewModel = communalTransitionViewModel;
mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
mMainDispatcher = mainDispatcher;
@@ -1615,11 +1625,21 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl();
if (viewRootImpl != null) {
- final DreamViewModel viewModel = mDreamViewModel.get();
- collectFlow(viewRootImpl.getView(), viewModel.getDreamAlpha(),
+ final DreamViewModel dreamViewModel = mDreamViewModel.get();
+ final CommunalTransitionViewModel communalViewModel =
+ mCommunalTransitionViewModel.get();
+ collectFlow(viewRootImpl.getView(), dreamViewModel.getDreamAlpha(),
getRemoteSurfaceAlphaApplier(), mMainDispatcher);
- collectFlow(viewRootImpl.getView(), viewModel.getTransitionEnded(),
+ collectFlow(viewRootImpl.getView(), dreamViewModel.getTransitionEnded(),
getFinishedCallbackConsumer(), mMainDispatcher);
+ collectFlow(viewRootImpl.getView(), communalViewModel.getShowByDefault(),
+ (showByDefault) ->
+ mShowCommunalByDefault = showByDefault, mMainDispatcher);
+ collectFlow(viewRootImpl.getView(),
+ communalViewModel.getTransitionFromOccludedEnded(),
+ getFinishedCallbackConsumer(), mMainDispatcher);
+ } else {
+ Log.e(TAG, "Keyguard ViewRootImpl is null");
}
}
// Most services aren't available until the system reaches the ready state, so we
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index a243b8eec264..7879ab6e1ed2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -39,6 +39,7 @@ import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingModule;
+import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -161,6 +162,7 @@ public interface KeyguardModule {
SystemClock systemClock,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamViewModel> dreamViewModel,
+ Lazy<CommunalTransitionViewModel> communalTransitionViewModel,
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
@@ -208,6 +210,7 @@ public interface KeyguardModule {
systemClock,
mainDispatcher,
dreamViewModel,
+ communalTransitionViewModel,
systemPropertiesHelper,
wmLockscreenVisibilityManager,
selectedUserInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
index 3f4d3a8544d0..6c29bce616bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.data.repository
+import android.content.Context
import android.os.UserHandle
import android.provider.Settings
import com.android.keyguard.ClockEventController
@@ -24,9 +25,12 @@ import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.res.R
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -47,7 +51,11 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
interface KeyguardClockRepository {
- /** clock size determined by notificationPanelViewController, LARGE or SMALL */
+ /**
+ * clock size determined by notificationPanelViewController, LARGE or SMALL
+ *
+ * @deprecated When scene container flag is on use clockSize from domain level.
+ */
val clockSize: StateFlow<Int>
/** clock size selected in picker, DYNAMIC or SMALL */
@@ -61,6 +69,9 @@ interface KeyguardClockRepository {
val previewClock: Flow<ClockController>
val clockEventController: ClockEventController
+
+ val shouldForceSmallClock: Boolean
+
fun setClockSize(@ClockSize size: Int)
}
@@ -73,6 +84,8 @@ constructor(
override val clockEventController: ClockEventController,
@Background private val backgroundDispatcher: CoroutineDispatcher,
@Application private val applicationScope: CoroutineScope,
+ @Application private val applicationContext: Context,
+ private val featureFlags: FeatureFlagsClassic,
) : KeyguardClockRepository {
/** Receive SMALL or LARGE clock should be displayed on keyguard. */
@@ -135,6 +148,12 @@ constructor(
clockRegistry.createCurrentClock()
}
+ override val shouldForceSmallClock: Boolean
+ get() =
+ featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE) &&
+ // True on small landscape screens
+ applicationContext.resources.getBoolean(R.bool.force_small_clock_on_lockscreen)
+
private fun getClockSize(): SettingsClockSize {
return if (
secureSettings.getIntForUser(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 1298fa5af033..462d8373a430 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -206,7 +206,11 @@ interface KeyguardRepository {
)
val keyguardDoneAnimationsFinished: Flow<Unit>
- /** Receive whether clock should be centered on lockscreen. */
+ /**
+ * Receive whether clock should be centered on lockscreen.
+ *
+ * @deprecated When scene container flag is on use clockShouldBeCentered from domain level.
+ */
val clockShouldBeCentered: Flow<Boolean>
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index b6289d44e2c3..ee589f4a235c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -87,12 +87,12 @@ constructor(
scope.launch {
keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
.filterRelevantKeyguardStateAnd { onTop -> !onTop }
- .sample(communalInteractor.isIdleOnCommunal, ::Pair)
- .collect { (_, isIdleOnCommunal) ->
+ .sample(communalInteractor.isIdleOnCommunal, communalInteractor.showByDefault)
+ .collect { (_, isIdleOnCommunal, showCommunalByDefault) ->
// Occlusion signals come from the framework, and should interrupt any
// existing transition
val to =
- if (isIdleOnCommunal) {
+ if (isIdleOnCommunal || showCommunalByDefault) {
KeyguardState.GLANCEABLE_HUB
} else {
KeyguardState.LOCKSCREEN
@@ -106,15 +106,16 @@ constructor(
.sample(
keyguardInteractor.isKeyguardShowing,
communalInteractor.isIdleOnCommunal,
+ communalInteractor.showByDefault,
)
- .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _) ->
+ .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _) ->
!isOccluded && isShowing
}
- .collect { (_, _, isIdleOnCommunal) ->
+ .collect { (_, _, isIdleOnCommunal, showCommunalByDefault) ->
// Occlusion signals come from the framework, and should interrupt any
// existing transition
val to =
- if (isIdleOnCommunal) {
+ if (isIdleOnCommunal || showCommunalByDefault) {
KeyguardState.GLANCEABLE_HUB
} else {
KeyguardState.LOCKSCREEN
@@ -175,6 +176,7 @@ constructor(
duration =
when (toState) {
KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+ KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -184,6 +186,7 @@ constructor(
const val TAG = "FromOccludedTransitionInteractor"
private val DEFAULT_DURATION = 500.milliseconds
val TO_LOCKSCREEN_DURATION = 933.milliseconds
+ val TO_GLANCEABLE_HUB_DURATION = 250.milliseconds
val TO_AOD_DURATION = DEFAULT_DURATION
val TO_DOZING_DURATION = DEFAULT_DURATION
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index d39bd3d5eb12..720baec54190 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -83,19 +83,12 @@ constructor(
private fun updateBlueprint() {
val useSplitShade =
splitShadeStateController.shouldUseSplitNotificationShade(context.resources)
- // TODO(b/326098079): Make ID a constant value.
- val useWeatherClockLayout =
- clockInteractor.currentClock.value?.config?.id == "DIGITAL_CLOCK_WEATHER" &&
- ComposeLockscreen.isEnabled
val blueprintId =
when {
- useWeatherClockLayout && useSplitShade -> SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
- useWeatherClockLayout -> WEATHER_CLOCK_BLUEPRINT_ID
useSplitShade && !ComposeLockscreen.isEnabled -> SplitShadeKeyguardBlueprint.ID
else -> DefaultKeyguardBlueprint.DEFAULT
}
-
transitionToBlueprint(blueprintId)
}
@@ -128,13 +121,4 @@ constructor(
fun getCurrentBlueprint(): KeyguardBlueprint {
return keyguardBlueprintRepository.blueprint.value
}
-
- companion object {
- /**
- * These values live here because classes in the composable package do not exist in some
- * systems.
- */
- const val WEATHER_CLOCK_BLUEPRINT_ID = "weather-clock"
- const val SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID = "split-shade-weather-clock"
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index d551c9b9a4de..f7f60a5a72a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -21,23 +21,48 @@ import android.util.Log
import com.android.keyguard.ClockEventController
import com.android.keyguard.KeyguardClockSwitch
import com.android.keyguard.KeyguardClockSwitch.ClockSize
+import com.android.keyguard.KeyguardClockSwitch.LARGE
+import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
+import com.android.systemui.util.kotlin.combine
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
private val TAG = KeyguardClockInteractor::class.simpleName
-/** Manages and ecapsulates the clock components of the lockscreen root view. */
+/** Manages and encapsulates the clock components of the lockscreen root view. */
@SysUISingleton
class KeyguardClockInteractor
@Inject
constructor(
+ mediaCarouselInteractor: MediaCarouselInteractor,
+ activeNotificationsInteractor: ActiveNotificationsInteractor,
+ shadeInteractor: ShadeInteractor,
+ keyguardInteractor: KeyguardInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ headsUpNotificationInteractor: HeadsUpNotificationInteractor,
+ @Application private val applicationScope: CoroutineScope,
private val keyguardClockRepository: KeyguardClockRepository,
) {
+ private val isOnAod: Flow<Boolean> =
+ keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.AOD }
val selectedClockSize: StateFlow<SettingsClockSize> = keyguardClockRepository.selectedClockSize
@@ -51,7 +76,64 @@ constructor(
var clock: ClockController? by keyguardClockRepository.clockEventController::clock
- val clockSize: StateFlow<Int> = keyguardClockRepository.clockSize
+ // TODO (b/333389512): Convert this into a more readable enum.
+ val clockSize: StateFlow<Int> =
+ if (SceneContainerFlag.isEnabled) {
+ combine(
+ shadeInteractor.shadeMode,
+ activeNotificationsInteractor.areAnyNotificationsPresent,
+ mediaCarouselInteractor.hasActiveMediaOrRecommendation,
+ keyguardInteractor.isDozing,
+ isOnAod,
+ ) { shadeMode, hasNotifs, hasMedia, isDozing, isOnAod ->
+ return@combine when {
+ keyguardClockRepository.shouldForceSmallClock && !isOnAod -> SMALL
+ shadeMode == ShadeMode.Single && (hasNotifs || hasMedia) -> SMALL
+ shadeMode == ShadeMode.Single -> LARGE
+ hasMedia && !isDozing -> SMALL
+ else -> LARGE
+ }
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = LARGE
+ )
+ } else {
+ SceneContainerFlag.assertInLegacyMode()
+ keyguardClockRepository.clockSize
+ }
+
+ val clockShouldBeCentered: Flow<Boolean> =
+ if (SceneContainerFlag.isEnabled) {
+ combine(
+ shadeInteractor.shadeMode,
+ activeNotificationsInteractor.areAnyNotificationsPresent,
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ isOnAod,
+ headsUpNotificationInteractor.isHeadsUpOrAnimatingAway,
+ keyguardInteractor.isDozing,
+ ) {
+ shadeMode,
+ areAnyNotificationsPresent,
+ isActiveDreamLockscreenHosted,
+ isOnAod,
+ isHeadsUp,
+ isDozing ->
+ when {
+ shadeMode != ShadeMode.Split -> true
+ !areAnyNotificationsPresent -> true
+ isActiveDreamLockscreenHosted -> true
+ // Pulsing notification appears on the right. Move clock left to avoid overlap.
+ isHeadsUp && isDozing -> false
+ else -> isOnAod
+ }
+ }
+ } else {
+ SceneContainerFlag.assertInLegacyMode()
+ keyguardInteractor.clockShouldBeCentered
+ }
+
fun setClockSize(@ClockSize size: Int) {
keyguardClockRepository.setClockSize(size)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 2182fe378d2f..c4769488646d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -210,7 +210,8 @@ constructor(
keyguardTransitionInteractor
.transitionValue(GONE)
.map { it == 1f }
- .onStart { emit(false) },
+ .onStart { emit(false) }
+ .distinctUntilChanged(),
repository.topClippingBounds
) { _, isGone, topClippingBounds ->
if (!isGone) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/RefactorKeyguardDismissIntent.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/RefactorKeyguardDismissIntent.kt
new file mode 100644
index 000000000000..a43eb710e880
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/RefactorKeyguardDismissIntent.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the refactor_keyguard_dismiss_intent flag. */
+@Suppress("NOTHING_TO_INLINE")
+object RefactorKeyguardDismissIntent {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.refactorKeyguardDismissIntent()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index e423fe0bd8a0..f46a207b273a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -25,7 +25,6 @@ import android.view.View
import androidx.core.view.isInvisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.tracing.coroutines.launch
import com.android.systemui.common.ui.view.LongPressHandlingView
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
@@ -35,15 +34,13 @@ import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
@ExperimentalCoroutinesApi
object DeviceEntryIconViewBinder {
- private const val TAG = "DeviceEntryIconViewBinder"
-
/**
* Updates UI for:
* - device entry containing view (parent view for the below views)
@@ -61,7 +58,6 @@ object DeviceEntryIconViewBinder {
bgViewModel: DeviceEntryBackgroundViewModel,
falsingManager: FalsingManager,
vibratorHelper: VibratorHelper,
- mainImmediateDispatcher: CoroutineDispatcher,
) {
DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()
val longPressHandlingView = view.longPressHandlingView
@@ -77,33 +73,31 @@ object DeviceEntryIconViewBinder {
view,
HapticFeedbackConstants.CONFIRM,
)
- applicationScope.launch("$TAG#viewModel.onLongPress") {
- viewModel.onLongPress()
- }
+ applicationScope.launch { viewModel.onLongPress() }
}
}
- view.repeatWhenAttached(mainImmediateDispatcher) {
+ view.repeatWhenAttached {
// Repeat on CREATED so that the view will always observe the entire
// GONE => AOD transition (even though the view may not be visible until the middle
// of the transition.
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch("$TAG#viewModel.isVisible") {
+ launch {
viewModel.isVisible.collect { isVisible ->
longPressHandlingView.isInvisible = !isVisible
}
}
- launch("$TAG#viewModel.isLongPressEnabled") {
+ launch {
viewModel.isLongPressEnabled.collect { isEnabled ->
longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
}
}
- launch("$TAG#viewModel.accessibilityDelegateHint") {
+ launch {
viewModel.accessibilityDelegateHint.collect { hint ->
view.accessibilityHintType = hint
}
}
- launch("$TAG#viewModel.useBackgroundProtection") {
+ launch {
viewModel.useBackgroundProtection.collect { useBackgroundProtection ->
if (useBackgroundProtection) {
bgView.visibility = View.VISIBLE
@@ -112,7 +106,7 @@ object DeviceEntryIconViewBinder {
}
}
}
- launch("$TAG#viewModel.burnInOffsets") {
+ launch {
viewModel.burnInOffsets.collect { burnInOffsets ->
view.translationX = burnInOffsets.x.toFloat()
view.translationY = burnInOffsets.y.toFloat()
@@ -120,17 +114,15 @@ object DeviceEntryIconViewBinder {
}
}
- launch("$TAG#viewModel.deviceEntryViewAlpha") {
- viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha }
- }
+ launch { viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha } }
}
}
- fgIconView.repeatWhenAttached(mainImmediateDispatcher) {
+ fgIconView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Start with an empty state
fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
- launch("$TAG#fgViewModel.viewModel") {
+ launch {
fgViewModel.viewModel.collect { viewModel ->
fgIconView.setImageState(
view.getIconState(viewModel.type, viewModel.useAodVariant),
@@ -150,10 +142,8 @@ object DeviceEntryIconViewBinder {
bgView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch("$TAG#bgViewModel.alpha") {
- bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha }
- }
- launch("$TAG#bgViewModel.color") {
+ launch { bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha } }
+ launch {
bgViewModel.color.collect { color ->
bgView.imageTintList = ColorStateList.valueOf(color)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 1b06a69213b9..6255f0d44609 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -26,7 +26,6 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.tracing.coroutines.launch
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.keyguard.MigrateClocksToBlueprint
@@ -38,10 +37,10 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
-import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
object KeyguardClockViewBinder {
- private const val TAG = "KeyguardClockViewBinder"
+ private val TAG = KeyguardClockViewBinder::class.simpleName!!
// When changing to new clock, we need to remove old clock views from burnInLayer
private var lastClock: ClockController? = null
@JvmStatic
@@ -51,12 +50,15 @@ object KeyguardClockViewBinder {
viewModel: KeyguardClockViewModel,
keyguardClockInteractor: KeyguardClockInteractor,
blueprintInteractor: KeyguardBlueprintInteractor,
- ): DisposableHandle {
- keyguardClockInteractor.clockEventController.registerListeners(keyguardRootView)
-
- return keyguardRootView.repeatWhenAttached {
+ ) {
+ keyguardRootView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ keyguardClockInteractor.clockEventController.registerListeners(keyguardRootView)
+ }
+ }
+ keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch("$TAG#viewModel.currentClock") {
+ launch {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.currentClock.collect { currentClock ->
cleanupClockViews(currentClock, keyguardRootView, viewModel.burnInLayer)
@@ -65,14 +67,14 @@ object KeyguardClockViewBinder {
applyConstraints(clockSection, keyguardRootView, true)
}
}
- launch("$TAG#viewModel.clockSize") {
+ launch {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.clockSize.collect {
updateBurnInLayer(keyguardRootView, viewModel)
blueprintInteractor.refreshBlueprint(Type.ClockSize)
}
}
- launch("$TAG#viewModel.clockShouldBeCentered") {
+ launch {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.clockShouldBeCentered.collect { clockShouldBeCentered ->
viewModel.currentClock.value?.let {
@@ -89,7 +91,7 @@ object KeyguardClockViewBinder {
}
}
}
- launch("$TAG#viewModel.isAodIconsVisible") {
+ launch {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.isAodIconsVisible.collect { isAodIconsVisible ->
viewModel.currentClock.value?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
index d5add61822b1..93b3ba561eb3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
@@ -19,9 +19,8 @@ import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
+import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
import com.android.systemui.log.core.LogLevel
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -38,11 +37,10 @@ constructor(
private val interactor: KeyguardDismissActionInteractor,
@Application private val scope: CoroutineScope,
private val keyguardLogger: KeyguardLogger,
- private val featureFlags: FeatureFlagsClassic,
) : CoreStartable {
override fun start() {
- if (!featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (!RefactorKeyguardDismissIntent.isEnabled) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
index 87d8164e1cb9..f77d01208dc2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
@@ -21,8 +21,8 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor
+import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.log.core.LogLevel
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -44,7 +44,7 @@ constructor(
) : CoreStartable {
override fun start() {
- if (!featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (!RefactorKeyguardDismissIntent.isEnabled) {
return
}
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 486320af45a2..3ff32bfcfd46 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
@@ -44,6 +44,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
+import com.android.systemui.shared.clocks.ClockRegistry
import com.android.systemui.util.Utils
import kotlin.reflect.KSuspendFunction1
@@ -77,37 +78,42 @@ object KeyguardPreviewClockViewBinder {
context: Context,
rootView: ConstraintLayout,
viewModel: KeyguardPreviewClockViewModel,
+ clockRegistry: ClockRegistry,
updateClockAppearance: KSuspendFunction1<ClockController, Unit>,
) {
rootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
+ var lastClock: ClockController? = null
launch("$TAG#viewModel.previewClock") {
- var lastClock: ClockController? = null
- viewModel.previewClock.collect { currentClock ->
- lastClock?.let { clock ->
- (clock.largeClock.layout.views + clock.smallClock.layout.views)
- .forEach { rootView.removeView(it) }
- }
- lastClock = currentClock
- updateClockAppearance(currentClock)
+ viewModel.previewClock.collect { currentClock ->
+ lastClock?.let { clock ->
+ (clock.largeClock.layout.views + clock.smallClock.layout.views)
+ .forEach { rootView.removeView(it) }
+ }
+ lastClock = currentClock
+ updateClockAppearance(currentClock)
- if (viewModel.shouldHighlightSelectedAffordance) {
- (currentClock.largeClock.layout.views +
- currentClock.smallClock.layout.views)
- .forEach { it.alpha = KeyguardPreviewRenderer.DIM_ALPHA }
- }
- currentClock.largeClock.layout.views.forEach {
- (it.parent as? ViewGroup)?.removeView(it)
- rootView.addView(it)
- }
+ if (viewModel.shouldHighlightSelectedAffordance) {
+ (currentClock.largeClock.layout.views +
+ currentClock.smallClock.layout.views)
+ .forEach { it.alpha = KeyguardPreviewRenderer.DIM_ALPHA }
+ }
+ currentClock.largeClock.layout.views.forEach {
+ (it.parent as? ViewGroup)?.removeView(it)
+ rootView.addView(it)
+ }
- currentClock.smallClock.layout.views.forEach {
- (it.parent as? ViewGroup)?.removeView(it)
- rootView.addView(it)
+ currentClock.smallClock.layout.views.forEach {
+ (it.parent as? ViewGroup)?.removeView(it)
+ rootView.addView(it)
+ }
+ applyPreviewConstraints(context, rootView, currentClock, viewModel)
}
- applyPreviewConstraints(context, rootView, currentClock, viewModel)
}
- }
+ .invokeOnCompletion {
+ // recover seed color especially for Transit clock
+ lastClock?.events?.onSeedColorChanged(clockRegistry.seedColor)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
index 49ae35a43c23..88d907469f69 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
@@ -32,7 +32,7 @@ object KeyguardPreviewSmartspaceViewBinder {
@JvmStatic
fun bind(
- context: Context,
+ previewContext: Context,
smartspace: View,
splitShadePreview: Boolean,
viewModel: KeyguardPreviewSmartspaceViewModel,
@@ -46,10 +46,12 @@ object KeyguardPreviewSmartspaceViewBinder {
SettingsClockSize.DYNAMIC ->
viewModel.getLargeClockSmartspaceTopPadding(
splitShadePreview,
+ previewContext,
)
SettingsClockSize.SMALL ->
viewModel.getSmallClockSmartspaceTopPadding(
splitShadePreview,
+ previewContext,
)
}
smartspace.setTopPadding(topPadding)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 6c21e6cdb3bc..abd79ab793d5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -30,7 +30,6 @@ import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.tracing.coroutines.launch
import com.android.settingslib.Utils
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.view.LaunchableImageView
@@ -42,11 +41,11 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.doOnEnd
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
/** This is only for a SINGLE Quick affordance */
object KeyguardQuickAffordanceViewBinder {
@@ -54,7 +53,6 @@ object KeyguardQuickAffordanceViewBinder {
private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
private const val SCALE_SELECTED_BUTTON = 1.23f
private const val DIM_ALPHA = 0.3f
- private const val TAG = "KeyguardQuickAffordanceViewBinder"
/**
* Defines interface for an object that acts as the binding between the view and its view-model.
@@ -76,15 +74,14 @@ object KeyguardQuickAffordanceViewBinder {
alpha: Flow<Float>,
falsingManager: FalsingManager?,
vibratorHelper: VibratorHelper?,
- mainImmediateDispatcher: CoroutineDispatcher,
messageDisplayer: (Int) -> Unit,
): Binding {
val button = view as ImageView
val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
val disposableHandle =
- view.repeatWhenAttached(mainImmediateDispatcher) {
+ view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch("$TAG#viewModel.collect") {
+ launch {
viewModel.collect { buttonModel ->
updateButton(
view = button,
@@ -96,7 +93,7 @@ object KeyguardQuickAffordanceViewBinder {
}
}
- launch("$TAG#updateButtonAlpha") {
+ launch {
updateButtonAlpha(
view = button,
viewModel = viewModel,
@@ -104,7 +101,7 @@ object KeyguardQuickAffordanceViewBinder {
)
}
- launch("$TAG#configurationBasedDimensions") {
+ launch {
configurationBasedDimensions.collect { dimensions ->
button.updateLayoutParams<ViewGroup.LayoutParams> {
width = dimensions.buttonSizePx.width
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 0249abd684cc..5ee35e4f8eb6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -33,7 +33,6 @@ import android.view.WindowInsets
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
-import com.android.app.tracing.coroutines.launch
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
import com.android.systemui.Flags.newAodTransition
@@ -41,6 +40,9 @@ import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.view.onApplyWindowInsets
+import com.android.systemui.common.ui.view.onLayoutChanged
+import com.android.systemui.common.ui.view.onTouchListener
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
@@ -64,12 +66,12 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
+import com.android.systemui.util.kotlin.DisposableHandles
import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
import kotlin.math.min
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
@@ -77,6 +79,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
/** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -97,18 +100,20 @@ object KeyguardRootViewBinder {
vibratorHelper: VibratorHelper?,
falsingManager: FalsingManager?,
keyguardViewMediator: KeyguardViewMediator?,
- mainImmediateDispatcher: CoroutineDispatcher,
): DisposableHandle {
- var onLayoutChangeListener: OnLayoutChange? = null
+ val disposables = DisposableHandles()
val childViews = mutableMapOf<Int, View>()
if (KeyguardBottomAreaRefactor.isEnabled) {
- view.setOnTouchListener { _, event ->
- if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) {
- viewModel.setRootViewLastTapPosition(Point(event.x.toInt(), event.y.toInt()))
+ disposables +=
+ view.onTouchListener { _, event ->
+ if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) {
+ viewModel.setRootViewLastTapPosition(
+ Point(event.x.toInt(), event.y.toInt())
+ )
+ }
+ false
}
- false
- }
}
val burnInParams = MutableStateFlow(BurnInParameters())
@@ -117,10 +122,10 @@ object KeyguardRootViewBinder {
alpha = { view.alpha },
)
- val disposableHandle =
- view.repeatWhenAttached(mainImmediateDispatcher) {
+ disposables +=
+ view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch("$TAG#occludingAppDeviceEntryMessageViewModel.message") {
+ launch {
occludingAppDeviceEntryMessageViewModel.message.collect { biometricMessage
->
if (biometricMessage?.message != null) {
@@ -139,7 +144,7 @@ object KeyguardRootViewBinder {
if (
KeyguardBottomAreaRefactor.isEnabled || DeviceEntryUdfpsRefactor.isEnabled
) {
- launch("$TAG#viewModel.alpha") {
+ launch {
viewModel.alpha(viewState).collect { alpha ->
view.alpha = alpha
if (KeyguardBottomAreaRefactor.isEnabled) {
@@ -151,21 +156,21 @@ object KeyguardRootViewBinder {
}
if (MigrateClocksToBlueprint.isEnabled) {
- launch("$TAG#viewModel.burnInLayerVisibility") {
+ launch {
viewModel.burnInLayerVisibility.collect { visibility ->
childViews[burnInLayerId]?.visibility = visibility
childViews[aodNotificationIconContainerId]?.visibility = visibility
}
}
- launch("$TAG#viewModel.burnInLayerAlpha") {
+ launch {
viewModel.burnInLayerAlpha.collect { alpha ->
childViews[statusViewId]?.alpha = alpha
childViews[aodNotificationIconContainerId]?.alpha = alpha
}
}
- launch("$TAG#viewModel.topClippingBounds") {
+ launch {
val clipBounds = Rect()
viewModel.topClippingBounds.collect { clipTop ->
if (clipTop == null) {
@@ -182,13 +187,13 @@ object KeyguardRootViewBinder {
}
}
- launch("$TAG#viewModel.lockscreenStateAlpha") {
+ launch {
viewModel.lockscreenStateAlpha(viewState).collect { alpha ->
childViews[statusViewId]?.alpha = alpha
}
}
- launch("$TAG#viewModel.translationY") {
+ launch {
// When translation happens in burnInLayer, it won't be weather clock
// large clock isn't added to burnInLayer due to its scale transition
// so we also need to add translation to it here
@@ -200,7 +205,7 @@ object KeyguardRootViewBinder {
}
}
- launch("$TAG#viewModel.translationX") {
+ launch {
viewModel.translationX.collect { state ->
val px = state.value ?: return@collect
when {
@@ -227,7 +232,7 @@ object KeyguardRootViewBinder {
}
}
- launch("$TAG#viewModel.scale") {
+ launch {
viewModel.scale.collect { scaleViewModel ->
if (scaleViewModel.scaleClockOnly) {
// For clocks except weather clock, we have scale transition
@@ -258,7 +263,7 @@ object KeyguardRootViewBinder {
}
if (NotificationIconContainerRefactor.isEnabled) {
- launch("$TAG#viewModel.isNotifIconContainerVisible") {
+ launch {
val iconsAppearTranslationPx =
configuration
.getDimensionPixelSize(R.dimen.shelf_appear_translation)
@@ -275,7 +280,7 @@ object KeyguardRootViewBinder {
}
interactionJankMonitor?.let { jankMonitor ->
- launch("$TAG#viewModel.goneToAodTransition") {
+ launch {
viewModel.goneToAodTransition.collect {
when (it.transitionState) {
TransitionState.STARTED -> {
@@ -301,7 +306,7 @@ object KeyguardRootViewBinder {
}
}
- launch("$TAG#shadeInteractor.isAnyFullyExpanded") {
+ launch {
shadeInteractor.isAnyFullyExpanded.collect { isFullyAnyExpanded ->
view.visibility =
if (isFullyAnyExpanded) {
@@ -312,12 +317,10 @@ object KeyguardRootViewBinder {
}
}
- launch("$TAG#burnInParams.collect") {
- burnInParams.collect { viewModel.updateBurnInParams(it) }
- }
+ launch { burnInParams.collect { viewModel.updateBurnInParams(it) } }
if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
- launch("$TAG#deviceEntryHapticsInteractor.playSuccessHaptic") {
+ launch {
deviceEntryHapticsInteractor.playSuccessHaptic.collect {
vibratorHelper.performHapticFeedback(
view,
@@ -327,7 +330,7 @@ object KeyguardRootViewBinder {
}
}
- launch("$TAG#deviceEntryHapticsInteractor.playErrorHaptic") {
+ launch {
deviceEntryHapticsInteractor.playErrorHaptic.collect {
vibratorHelper.performHapticFeedback(
view,
@@ -346,8 +349,7 @@ object KeyguardRootViewBinder {
}
}
- onLayoutChangeListener = OnLayoutChange(viewModel, childViews, burnInParams)
- view.addOnLayoutChangeListener(onLayoutChangeListener)
+ disposables += view.onLayoutChanged(OnLayoutChange(viewModel, childViews, burnInParams))
// Views will be added or removed after the call to bind(). This is needed to avoid many
// calls to findViewById
@@ -362,24 +364,21 @@ object KeyguardRootViewBinder {
}
}
)
-
- view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
- val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
- burnInParams.update { current ->
- current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
- }
- insets
+ disposables += DisposableHandle {
+ view.setOnHierarchyChangeListener(null)
+ childViews.clear()
}
- return object : DisposableHandle {
- override fun dispose() {
- disposableHandle.dispose()
- view.removeOnLayoutChangeListener(onLayoutChangeListener)
- view.setOnHierarchyChangeListener(null)
- view.setOnApplyWindowInsetsListener(null)
- childViews.clear()
+ disposables +=
+ view.onApplyWindowInsets { _: View, insets: WindowInsets ->
+ val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
+ burnInParams.update { current ->
+ current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+ }
+ insets
}
- }
+
+ return disposables
}
/**
@@ -586,5 +585,4 @@ object KeyguardRootViewBinder {
private const val ID = "occluding_app_device_entry_unlock_msg"
private const val AOD_ICONS_APPEAR_DURATION: Long = 200
- private const val TAG = "KeyguardRootViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 42bd4aff1dc4..bda5be4f6533 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -327,9 +327,12 @@ constructor(
smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView)
val topPadding: Int =
- smartspaceViewModel.getLargeClockSmartspaceTopPadding(previewInSplitShade())
- val startPadding: Int = smartspaceViewModel.getSmartspaceStartPadding()
- val endPadding: Int = smartspaceViewModel.getSmartspaceEndPadding()
+ smartspaceViewModel.getLargeClockSmartspaceTopPadding(
+ previewInSplitShade(),
+ previewContext,
+ )
+ val startPadding: Int = smartspaceViewModel.getSmartspaceStartPadding(previewContext)
+ val endPadding: Int = smartspaceViewModel.getSmartspaceEndPadding(previewContext)
smartSpaceView?.let {
it.setPaddingRelative(startPadding, topPadding, endPadding, 0)
@@ -387,7 +390,6 @@ constructor(
null, // device entry haptics not required for preview mode
null, // falsing manager not required for preview mode
null, // keyguard view mediator is not required for preview mode
- mainDispatcher,
)
}
rootView.addView(
@@ -411,10 +413,11 @@ constructor(
setUpClock(previewContext, rootView)
if (MigrateClocksToBlueprint.isEnabled) {
KeyguardPreviewClockViewBinder.bind(
- context,
+ previewContext,
keyguardRootView,
clockViewModel,
- ::updateClockAppearance
+ clockRegistry,
+ ::updateClockAppearance,
)
} else {
KeyguardPreviewClockViewBinder.bind(
@@ -429,7 +432,7 @@ constructor(
smartSpaceView?.let {
KeyguardPreviewSmartspaceViewBinder.bind(
- context,
+ previewContext,
it,
previewInSplitShade(),
smartspaceViewModel
@@ -454,7 +457,6 @@ constructor(
alpha = flowOf(1f),
falsingManager = falsingManager,
vibratorHelper = vibratorHelper,
- mainImmediateDispatcher = mainDispatcher,
) { message ->
indicationController.showTransientIndication(message)
}
@@ -469,7 +471,6 @@ constructor(
alpha = flowOf(1f),
falsingManager = falsingManager,
vibratorHelper = vibratorHelper,
- mainImmediateDispatcher = mainDispatcher,
) { message ->
indicationController.showTransientIndication(message)
}
@@ -621,7 +622,9 @@ constructor(
}
private suspend fun updateClockAppearance(clock: ClockController) {
- clockController.clock = clock
+ if (!MigrateClocksToBlueprint.isEnabled) {
+ clockController.clock = clock
+ }
val colors = wallpaperColors
if (clockRegistry.seedColor == null && colors != null) {
// Seed color null means users do not override any color on the clock. The default
@@ -639,6 +642,11 @@ constructor(
if (isWallpaperDark) lightClockColor else darkClockColor
)
}
+ // In clock preview, we should have a seed color for clock
+ // before setting clock to clockEventController to avoid updateColor with seedColor == null
+ if (MigrateClocksToBlueprint.isEnabled) {
+ clockController.clock = clock
+ }
}
private fun onClockChanged() {
if (MigrateClocksToBlueprint.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
index b4e57cc93962..04ac7bf1178e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
@@ -17,13 +17,9 @@
package com.android.systemui.keyguard.ui.view.layout.blueprints
-import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
-import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
-import com.android.systemui.keyguard.shared.model.KeyguardSection
import dagger.Binds
import dagger.Module
-import dagger.Provides
import dagger.multibindings.IntoSet
@Module
@@ -45,26 +41,4 @@ abstract class KeyguardBlueprintModule {
abstract fun bindShortcutsBesideUdfpsLockscreenBlueprint(
shortcutsBesideUdfpsLockscreenBlueprint: ShortcutsBesideUdfpsKeyguardBlueprint
): KeyguardBlueprint
-
- companion object {
- /** This is a place holder for weather clock in compose. */
- @Provides
- @IntoSet
- fun bindWeatherClockBlueprintPlaceHolder(): KeyguardBlueprint {
- return object : KeyguardBlueprint {
- override val id: String = WEATHER_CLOCK_BLUEPRINT_ID
- override val sections: List<KeyguardSection> = listOf()
- }
- }
-
- /** This is a place holder for weather clock in compose. */
- @Provides
- @IntoSet
- fun bindSplitShadeWeatherClockBlueprintPlaceHolder(): KeyguardBlueprint {
- return object : KeyguardBlueprint {
- override val id: String = SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
- override val sections: List<KeyguardSection> = listOf()
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
index 5404729d1819..2e9663897f89 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
@@ -36,7 +36,6 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
class AlignShortcutsToUdfpsSection
@Inject
@@ -48,7 +47,6 @@ constructor(
private val falsingManager: FalsingManager,
private val indicationController: KeyguardIndicationController,
private val vibratorHelper: VibratorHelper,
- @Main private val mainImmediateDispatcher: CoroutineDispatcher,
) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
if (KeyguardBottomAreaRefactor.isEnabled) {
@@ -66,7 +64,6 @@ constructor(
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
- mainImmediateDispatcher,
) {
indicationController.showTransientIndication(it)
}
@@ -77,7 +74,6 @@ constructor(
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
- mainImmediateDispatcher,
) {
indicationController.showTransientIndication(it)
}
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 e0bf8152d11f..78a1fcfe4258 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
@@ -45,7 +45,6 @@ import com.android.systemui.res.R
import com.android.systemui.shared.R as sharedR
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.DisposableHandle
internal fun ConstraintSet.setVisibility(
views: Iterable<View>,
@@ -66,23 +65,19 @@ constructor(
val smartspaceViewModel: KeyguardSmartspaceViewModel,
val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
) : KeyguardSection() {
- private var handle: DisposableHandle? = null
-
override fun addViews(constraintLayout: ConstraintLayout) {}
override fun bindData(constraintLayout: ConstraintLayout) {
if (!MigrateClocksToBlueprint.isEnabled) {
return
}
- handle?.dispose()
- handle =
- KeyguardClockViewBinder.bind(
- this,
- constraintLayout,
- keyguardClockViewModel,
- clockInteractor,
- blueprintInteractor.get()
- )
+ KeyguardClockViewBinder.bind(
+ this,
+ constraintLayout,
+ keyguardClockViewModel,
+ clockInteractor,
+ blueprintInteractor.get()
+ )
}
override fun applyConstraints(constraintSet: ConstraintSet) {
@@ -94,13 +89,7 @@ constructor(
}
}
- override fun removeViews(constraintLayout: ConstraintLayout) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
- handle?.dispose()
- handle = null
- }
+ override fun removeViews(constraintLayout: ConstraintLayout) {}
private fun buildConstraints(
clock: ClockController,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 865e989c5b68..29041d1665c3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -30,7 +30,6 @@ import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -48,7 +47,6 @@ import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -69,7 +67,6 @@ constructor(
private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
private val falsingManager: Lazy<FalsingManager>,
private val vibratorHelper: Lazy<VibratorHelper>,
- @Main private val mainImmediateDispatcher: CoroutineDispatcher,
) : KeyguardSection() {
private val deviceEntryIconViewId = R.id.device_entry_icon_view
@@ -107,7 +104,6 @@ constructor(
deviceEntryBackgroundViewModel.get(),
falsingManager.get(),
vibratorHelper.get(),
- mainImmediateDispatcher,
)
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 27ca5cdbad17..45b82576c6c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -35,7 +35,6 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
class DefaultShortcutsSection
@Inject
@@ -47,7 +46,6 @@ constructor(
private val falsingManager: FalsingManager,
private val indicationController: KeyguardIndicationController,
private val vibratorHelper: VibratorHelper,
- @Main private val mainImmediateDispatcher: CoroutineDispatcher,
) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
if (KeyguardBottomAreaRefactor.isEnabled) {
@@ -65,7 +63,6 @@ constructor(
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
- mainImmediateDispatcher,
) {
indicationController.showTransientIndication(it)
}
@@ -76,7 +73,6 @@ constructor(
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
- mainImmediateDispatcher,
) {
indicationController.showTransientIndication(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
index fe88b8169c89..6d0f96c2f635 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -18,9 +18,8 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
+import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.ScrimAlpha
@@ -44,13 +43,12 @@ constructor(
private val statusBarStateController: SysuiStatusBarStateController,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
- private val featureFlags: FeatureFlagsClassic,
private val shadeInteractor: ShadeInteractor,
private val animationFlow: KeyguardTransitionAnimationFlow,
) {
/** Common fade for scrim alpha values during *BOUNCER->GONE */
fun scrimAlpha(duration: Duration, fromState: KeyguardState): Flow<ScrimAlpha> {
- return if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ return if (RefactorKeyguardDismissIntent.isEnabled) {
keyguardDismissActionInteractor
.get()
.willAnimateDismissActionOnLockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index c9251c7c5473..f6da033f1bd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -26,7 +26,6 @@ import com.android.systemui.customization.R as customizationR
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import com.android.systemui.res.R
@@ -46,11 +45,10 @@ import kotlinx.coroutines.flow.stateIn
class KeyguardClockViewModel
@Inject
constructor(
- keyguardInteractor: KeyguardInteractor,
- private val keyguardClockInteractor: KeyguardClockInteractor,
+ keyguardClockInteractor: KeyguardClockInteractor,
@Application private val applicationScope: CoroutineScope,
notifsKeyguardInteractor: NotificationsKeyguardInteractor,
- @VisibleForTesting val shadeInteractor: ShadeInteractor,
+ @get:VisibleForTesting val shadeInteractor: ShadeInteractor,
) {
var burnInLayer: Layer? = null
val useLargeClock: Boolean
@@ -99,7 +97,7 @@ constructor(
)
val clockShouldBeCentered: StateFlow<Boolean> =
- keyguardInteractor.clockShouldBeCentered.stateIn(
+ keyguardClockInteractor.clockShouldBeCentered.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
initialValue = false
@@ -113,18 +111,38 @@ constructor(
)
val currentClockLayout: StateFlow<ClockLayout> =
- combine(isLargeClockVisible, clockShouldBeCentered, shadeInteractor.shadeMode) {
+ combine(
isLargeClockVisible,
clockShouldBeCentered,
- shadeMode ->
+ shadeInteractor.shadeMode,
+ currentClock
+ ) { isLargeClockVisible, clockShouldBeCentered, shadeMode, currentClock ->
val shouldUseSplitShade = shadeMode == ShadeMode.Split
- when {
- shouldUseSplitShade && clockShouldBeCentered -> ClockLayout.LARGE_CLOCK
- shouldUseSplitShade && isLargeClockVisible ->
- ClockLayout.SPLIT_SHADE_LARGE_CLOCK
- shouldUseSplitShade -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK
- isLargeClockVisible -> ClockLayout.LARGE_CLOCK
- else -> ClockLayout.SMALL_CLOCK
+ // TODO(b/326098079): make id a constant field in config
+ if (currentClock?.config?.id == "DIGITAL_CLOCK_WEATHER") {
+ val weatherClockLayout =
+ when {
+ shouldUseSplitShade && clockShouldBeCentered ->
+ ClockLayout.WEATHER_LARGE_CLOCK
+ shouldUseSplitShade && isLargeClockVisible ->
+ ClockLayout.SPLIT_SHADE_WEATHER_LARGE_CLOCK
+ shouldUseSplitShade -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK
+ isLargeClockVisible -> ClockLayout.WEATHER_LARGE_CLOCK
+ else -> ClockLayout.SMALL_CLOCK
+ }
+ weatherClockLayout
+ } else {
+ val clockLayout =
+ when {
+ shouldUseSplitShade && clockShouldBeCentered -> ClockLayout.LARGE_CLOCK
+ shouldUseSplitShade && isLargeClockVisible ->
+ ClockLayout.SPLIT_SHADE_LARGE_CLOCK
+ shouldUseSplitShade -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK
+ isLargeClockVisible -> ClockLayout.LARGE_CLOCK
+ else -> ClockLayout.SMALL_CLOCK
+ }
+
+ clockLayout
}
}
.stateIn(
@@ -179,5 +197,7 @@ constructor(
SMALL_CLOCK,
SPLIT_SHADE_LARGE_CLOCK,
SPLIT_SHADE_SMALL_CLOCK,
+ WEATHER_LARGE_CLOCK,
+ SPLIT_SHADE_WEATHER_LARGE_CLOCK,
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
index 4f2c6f576904..730015202a21 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
@@ -16,13 +16,10 @@
package com.android.systemui.keyguard.ui.viewmodel
-import android.content.Context
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import com.android.systemui.plugins.clocks.ClockController
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
@@ -31,9 +28,7 @@ import kotlinx.coroutines.flow.map
class KeyguardPreviewClockViewModel
@Inject
constructor(
- @Application private val context: Context,
interactor: KeyguardClockInteractor,
- @Application private val applicationScope: CoroutineScope,
) {
var shouldHighlightSelectedAffordance: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
index b57e3ecbe05b..528b14c6bbd7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import com.android.systemui.res.R
@@ -31,7 +30,6 @@ import kotlinx.coroutines.flow.map
class KeyguardPreviewSmartspaceViewModel
@Inject
constructor(
- @Application private val context: Context,
interactor: KeyguardClockInteractor,
val smartspaceViewModel: KeyguardSmartspaceViewModel,
val clockViewModel: KeyguardClockViewModel,
@@ -55,29 +53,29 @@ constructor(
}
}
- fun getSmartspaceStartPadding(): Int {
+ fun getSmartspaceStartPadding(context: Context): Int {
return KeyguardSmartspaceViewModel.getSmartspaceStartMargin(context)
}
- fun getSmartspaceEndPadding(): Int {
+ fun getSmartspaceEndPadding(context: Context): Int {
return KeyguardSmartspaceViewModel.getSmartspaceEndMargin(context)
}
- fun getSmallClockSmartspaceTopPadding(splitShadePreview: Boolean): Int {
- return getSmallClockTopPadding(splitShadePreview) +
+ fun getSmallClockSmartspaceTopPadding(splitShadePreview: Boolean, context: Context): Int {
+ return getSmallClockTopPadding(splitShadePreview, context) +
context.resources.getDimensionPixelSize(
com.android.systemui.customization.R.dimen.small_clock_height
)
}
- fun getLargeClockSmartspaceTopPadding(splitShadePreview: Boolean): Int {
- return getSmallClockTopPadding(splitShadePreview)
+ fun getLargeClockSmartspaceTopPadding(splitShadePreview: Boolean, context: Context): Int {
+ return getSmallClockTopPadding(splitShadePreview, context)
}
/*
* SmallClockTopPadding decides the top position of smartspace
*/
- private fun getSmallClockTopPadding(splitShadePreview: Boolean): Int {
+ private fun getSmallClockTopPadding(splitShadePreview: Boolean, context: Context): Int {
return with(context.resources) {
if (splitShadePreview) {
getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 64e15659d08b..ac67f94fa9cf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -185,8 +185,12 @@ constructor(
.transitionValue(OCCLUDED)
.map { it == 1f }
.onStart { emit(false) },
- ) { isIdleOnCommunal, isGone, isOccluded ->
- isIdleOnCommunal || isGone || isOccluded
+ keyguardTransitionInteractor
+ .transitionValue(KeyguardState.DREAMING)
+ .map { it == 1f }
+ .onStart { emit(false) },
+ ) { isIdleOnCommunal, isGone, isOccluded, isDreaming ->
+ isIdleOnCommunal || isGone || isOccluded || isDreaming
}
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 993e81bfbf69..d4c8456e0d71 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -37,6 +37,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the lockscreen scene. */
@@ -52,16 +54,23 @@ constructor(
val notifications: NotificationsPlaceholderViewModel,
) {
val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- combine(
- deviceEntryInteractor.isUnlocked,
- communalInteractor.isCommunalAvailable,
- shadeInteractor.shadeMode,
- ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
- destinationScenes(
- isDeviceUnlocked = isDeviceUnlocked,
- isCommunalAvailable = isCommunalAvailable,
- shadeMode = shadeMode,
- )
+ shadeInteractor.isShadeTouchable
+ .flatMapLatest { isShadeTouchable ->
+ if (!isShadeTouchable) {
+ flowOf(emptyMap())
+ } else {
+ combine(
+ deviceEntryInteractor.isUnlocked,
+ communalInteractor.isCommunalAvailable,
+ shadeInteractor.shadeMode,
+ ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+ destinationScenes(
+ isDeviceUnlocked = isDeviceUnlocked,
+ isCommunalAvailable = isCommunalAvailable,
+ shadeMode = shadeMode,
+ )
+ }
+ }
}
.stateIn(
scope = applicationScope,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index a09d58ac381b..3a19780c7017 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -21,21 +21,15 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-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.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.res.R
-import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
/**
* Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -49,8 +43,6 @@ constructor(
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
configurationInteractor: ConfigurationInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
- keyguardInteractor: KeyguardInteractor,
- keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : DeviceEntryIconTransition {
private val transitionAnimation =
@@ -82,28 +74,11 @@ constructor(
/** Lockscreen views alpha */
val lockscreenAlpha: Flow<Float> =
- merge(
- transitionAnimation.sharedFlow(
- startTime = 233.milliseconds,
- duration = 250.milliseconds,
- onStep = { it },
- name = "OCCLUDED->LOCKSCREEN: lockscreenAlpha",
- ),
- // Required to fix a bug where the shade expands while lockscreenAlpha=1f, due to a call
- // to setOccluded(false) triggering a reset() call in KeyguardViewMediator. The
- // permanent solution is to only expand the shade once the keyguard transition from
- // OCCLUDED starts, but that requires more refactoring of expansion amounts. For now,
- // emit alpha = 0f for OCCLUDED -> LOCKSCREEN whenever isOccluded flips from true to
- // false while currentState == OCCLUDED, so that alpha = 0f when that expansion occurs.
- // TODO(b/332946323): Remove this once it's no longer needed.
- keyguardInteractor.isKeyguardOccluded
- .pairwise()
- .filter { (wasOccluded, isOccluded) ->
- wasOccluded &&
- !isOccluded &&
- keyguardTransitionInteractor.getCurrentState() == KeyguardState.OCCLUDED
- }
- .map { 0f }
+ transitionAnimation.sharedFlow(
+ startTime = 233.milliseconds,
+ duration = 250.milliseconds,
+ onStep = { it },
+ name = "OCCLUDED->LOCKSCREEN: lockscreenAlpha",
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 05878265dd6d..a08a234f85d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -18,10 +18,9 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
+import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.ScrimAlpha
@@ -46,7 +45,6 @@ constructor(
private val statusBarStateController: SysuiStatusBarStateController,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
- featureFlags: FeatureFlagsClassic,
bouncerToGoneFlows: BouncerToGoneFlows,
animationFlow: KeyguardTransitionAnimationFlow,
) {
@@ -82,7 +80,7 @@ constructor(
/** Bouncer container alpha */
val bouncerAlpha: Flow<Float> =
- if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (RefactorKeyguardDismissIntent.isEnabled) {
keyguardDismissActionInteractor
.get()
.willAnimateDismissActionOnLockscreen
@@ -106,7 +104,7 @@ constructor(
/** Lockscreen alpha */
val lockscreenAlpha: Flow<Float> =
- if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (RefactorKeyguardDismissIntent.isEnabled) {
keyguardDismissActionInteractor
.get()
.willAnimateDismissActionOnLockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
index df34169c9a50..9dc5900296c2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
@@ -19,7 +19,9 @@ package com.android.systemui.media.controls.data.repository
import com.android.internal.logging.InstanceId
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -46,6 +48,16 @@ class MediaFilterRepository @Inject constructor() {
MutableStateFlow(LinkedHashMap())
val allUserEntries: StateFlow<Map<String, MediaData>> = _allUserEntries.asStateFlow()
+ private val _mediaDataLoadedStates: MutableStateFlow<List<MediaDataLoadingModel>> =
+ MutableStateFlow(mutableListOf())
+ val mediaDataLoadedStates: StateFlow<List<MediaDataLoadingModel>> =
+ _mediaDataLoadedStates.asStateFlow()
+
+ private val _recommendationsLoadingState: MutableStateFlow<SmartspaceMediaLoadingModel> =
+ MutableStateFlow(SmartspaceMediaLoadingModel.Unknown)
+ val recommendationsLoadingState: StateFlow<SmartspaceMediaLoadingModel> =
+ _recommendationsLoadingState.asStateFlow()
+
fun addMediaEntry(key: String, data: MediaData) {
val entries = LinkedHashMap<String, MediaData>(_allUserEntries.value)
entries[key] = data
@@ -110,4 +122,25 @@ class MediaFilterRepository @Inject constructor() {
fun setReactivatedId(instanceId: InstanceId?) {
_reactivatedId.value = instanceId
}
+
+ fun addMediaDataLoadingState(mediaDataLoadingModel: MediaDataLoadingModel) {
+ // Filter out previous loading state that has same [InstanceId].
+ val loadedStates =
+ _mediaDataLoadedStates.value.filter { loadedModel ->
+ loadedModel !is MediaDataLoadingModel.Loaded ||
+ !loadedModel.equalInstanceIds(mediaDataLoadingModel)
+ }
+
+ _mediaDataLoadedStates.value =
+ loadedStates +
+ if (mediaDataLoadingModel is MediaDataLoadingModel.Loaded) {
+ listOf(mediaDataLoadingModel)
+ } else {
+ emptyList()
+ }
+ }
+
+ fun setRecommedationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) {
+ _recommendationsLoadingState.value = smartspaceMediaLoadingModel
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
index d40069c4b3da..a30e5826529a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
@@ -28,7 +28,9 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_RESUME
import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.settings.UserTracker
@@ -67,9 +69,6 @@ constructor(
private val mediaFlags: MediaFlags,
private val mediaFilterRepository: MediaFilterRepository,
) : MediaDataManager.Listener {
- private val _listeners: MutableSet<Listener> = mutableSetOf()
- val listeners: Set<Listener>
- get() = _listeners.toSet()
lateinit var mediaDataManager: MediaDataManager
// Ensure the field (and associated reference) isn't removed during optimization.
@@ -111,8 +110,9 @@ constructor(
mediaFilterRepository.addSelectedUserMediaEntry(data)
- // Notify listeners
- listeners.forEach { it.onMediaDataLoaded(data.instanceId) }
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Loaded(data.instanceId)
+ )
}
override fun onSmartspaceMediaDataLoaded(
@@ -159,7 +159,7 @@ constructor(
// reactivate.
if (shouldReactivate) {
val lastActiveId = sorted.lastKey() // most recently active id
- // Notify listeners to consider this media active
+ // Update loading state to consider this media active
Log.d(TAG, "reactivating $lastActiveId instead of smartspace")
mediaFilterRepository.setReactivatedId(lastActiveId)
val mediaData = sorted[lastActiveId]!!.copy(active = true)
@@ -168,15 +168,9 @@ constructor(
mediaData.packageName,
mediaData.instanceId
)
- listeners.forEach {
- it.onMediaDataLoaded(
- lastActiveId,
- receivedSmartspaceCardLatency =
- (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis)
- .toInt(),
- isSsReactivated = true
- )
- }
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Loaded(lastActiveId)
+ )
}
} else if (data.isActive) {
// Mark to prioritize Smartspace card if no recent media.
@@ -192,15 +186,18 @@ constructor(
smartspaceMediaData.packageName,
smartspaceMediaData.instanceId
)
- listeners.forEach { it.onSmartspaceMediaDataLoaded(key, shouldPrioritizeMutable) }
+ mediaFilterRepository.setRecommedationsLoadingState(
+ SmartspaceMediaLoadingModel.Loaded(key, shouldPrioritizeMutable)
+ )
}
override fun onMediaDataRemoved(key: String) {
mediaFilterRepository.removeMediaEntry(key)?.let { mediaData ->
val instanceId = mediaData.instanceId
mediaFilterRepository.removeSelectedUserMediaEntry(instanceId)?.let {
- // Only notify listeners if something actually changed
- listeners.forEach { it.onMediaDataRemoved(instanceId) }
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Removed(instanceId)
+ )
}
}
}
@@ -210,11 +207,11 @@ constructor(
mediaFilterRepository.reactivatedId.value?.let { lastActiveId ->
mediaFilterRepository.setReactivatedId(null)
Log.d(TAG, "expiring reactivated key $lastActiveId")
- // Notify listeners to update with actual active value
+ // Update loading state with actual active value
mediaFilterRepository.selectedUserEntries.value[lastActiveId]?.let {
- listeners.forEach { listener ->
- listener.onMediaDataLoaded(lastActiveId, immediately)
- }
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Loaded(lastActiveId, immediately)
+ )
}
}
@@ -227,7 +224,9 @@ constructor(
)
)
}
- listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
+ mediaFilterRepository.setRecommedationsLoadingState(
+ SmartspaceMediaLoadingModel.Removed(key, immediately)
+ )
}
@VisibleForTesting
@@ -238,29 +237,37 @@ constructor(
// Only remove media when the profile is unavailable.
if (DEBUG) Log.d(TAG, "Removing $key after profile change")
mediaFilterRepository.removeSelectedUserMediaEntry(data.instanceId, data)
- listeners.forEach { listener -> listener.onMediaDataRemoved(data.instanceId) }
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Removed(data.instanceId)
+ )
}
}
}
@VisibleForTesting
internal fun handleUserSwitched() {
- // If the user changes, remove all current MediaData objects and inform listeners
- val listenersCopy = listeners
+ // If the user changes, remove all current MediaData objects.
val keyCopy = mediaFilterRepository.selectedUserEntries.value.keys.toMutableList()
- // Clear the list first, to make sure callbacks from listeners if we have any entries
- // are up to date
+ // Clear the list first and update loading state to remove media from UI.
mediaFilterRepository.clearSelectedUserMedia()
keyCopy.forEach { instanceId ->
if (DEBUG) Log.d(TAG, "Removing $instanceId after user change")
- listenersCopy.forEach { listener -> listener.onMediaDataRemoved(instanceId) }
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Removed(instanceId)
+ )
}
mediaFilterRepository.allUserEntries.value.forEach { (key, data) ->
if (lockscreenUserManager.isCurrentProfile(data.userId)) {
- if (DEBUG) Log.d(TAG, "Re-adding $key after user change")
+ if (DEBUG)
+ Log.d(
+ TAG,
+ "Re-adding $key with instanceId=${data.instanceId} after user change"
+ )
mediaFilterRepository.addSelectedUserMediaEntry(data)
- listenersCopy.forEach { listener -> listener.onMediaDataLoaded(data.instanceId) }
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Loaded(data.instanceId)
+ )
}
}
}
@@ -310,12 +317,6 @@ constructor(
}
}
- /** Add a listener for filtered [MediaData] changes */
- fun addListener(listener: Listener) = _listeners.add(listener)
-
- /** Remove a listener that was registered with addListener */
- fun removeListener(listener: Listener) = _listeners.remove(listener)
-
/**
* Return the time since last active for the most-recent media.
*
@@ -335,48 +336,6 @@ constructor(
return sortedEntries[lastActiveInstanceId]?.let { now - it.lastActive } ?: Long.MAX_VALUE
}
- interface Listener {
- /**
- * Called whenever there's new MediaData Loaded for the consumption in views.
- *
- * @param immediately indicates should apply the UI changes immediately, otherwise wait
- * until the next refresh-round before UI becomes visible. True by default to take in
- * place immediately.
- * @param receivedSmartspaceCardLatency is the latency between headphone connects and sysUI
- * displays Smartspace media targets. Will be 0 if the data is not activated by Smartspace
- * signal.
- * @param isSsReactivated indicates resume media card is reactivated by Smartspace
- * recommendation signal
- */
- fun onMediaDataLoaded(
- instanceId: InstanceId,
- immediately: Boolean = true,
- receivedSmartspaceCardLatency: Int = 0,
- isSsReactivated: Boolean = false,
- )
-
- /**
- * Called whenever there's new Smartspace media data loaded.
- *
- * @param shouldPrioritize indicates the sorting priority of the Smartspace card. If true,
- * it will be prioritized as the first card. Otherwise, it will show up as the last card
- * as default.
- */
- fun onSmartspaceMediaDataLoaded(key: String, shouldPrioritize: Boolean = false)
-
- /** Called whenever a previously existing Media notification was removed. */
- fun onMediaDataRemoved(instanceId: InstanceId)
-
- /**
- * Called whenever a previously existing Smartspace media data was removed.
- *
- * @param immediately indicates should apply the UI changes immediately, otherwise wait
- * until the next refresh-round before UI becomes visible. True by default to take in
- * place immediately.
- */
- fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean = true)
- }
-
companion object {
/**
* Maximum age of a media control to re-activate on smartspace signal. If there is no media
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index 7dbca0ae4cda..cdcf3636e148 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -34,11 +34,14 @@ import com.android.systemui.media.controls.domain.pipeline.MediaDeviceManager
import com.android.systemui.media.controls.domain.pipeline.MediaSessionBasedFilter
import com.android.systemui.media.controls.domain.pipeline.MediaTimeoutListener
import com.android.systemui.media.controls.domain.resume.MediaResumeListener
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
import com.android.systemui.media.controls.util.MediaFlags
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -109,6 +112,14 @@ constructor(
.distinctUntilChanged()
.stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
+ /** The most recent list of loaded media controls. */
+ val mediaDataLoadedStates: Flow<List<MediaDataLoadingModel>> =
+ mediaFilterRepository.mediaDataLoadedStates
+
+ /** The most recent change to loaded media recommendations. */
+ val recommendationsLoadingState: Flow<SmartspaceMediaLoadingModel> =
+ mediaFilterRepository.recommendationsLoadingState
+
override fun start() {
if (!mediaFlags.isMediaControlsRefactorEnabled()) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt
new file mode 100644
index 000000000000..bd42a4df7262
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.shared.model
+
+import com.android.internal.logging.InstanceId
+
+/** Models media data loading state. */
+sealed class MediaDataLoadingModel {
+ /** The initial loading state when no media data has yet loaded. */
+ data object Unknown : MediaDataLoadingModel()
+
+ /** Media data has been loaded. */
+ data class Loaded(
+ val instanceId: InstanceId,
+ val immediatelyUpdateUi: Boolean = true,
+ ) : MediaDataLoadingModel() {
+
+ /** Returns true if [other] has the same instance id, false otherwise. */
+ fun equalInstanceIds(other: MediaDataLoadingModel): Boolean {
+ return when (other) {
+ is Loaded -> other.instanceId == instanceId
+ is Removed -> other.instanceId == instanceId
+ Unknown -> false
+ }
+ }
+ }
+
+ /** Media data has been removed. */
+ data class Removed(
+ val instanceId: InstanceId,
+ ) : MediaDataLoadingModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaLoadingModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaLoadingModel.kt
new file mode 100644
index 000000000000..6c1e536f8c02
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaLoadingModel.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.shared.model
+
+/** Models smartspace media loading state. */
+sealed class SmartspaceMediaLoadingModel {
+ /** The initial loading state when no smartspace media has yet loaded. */
+ data object Unknown : SmartspaceMediaLoadingModel()
+
+ /** Smartspace media has been loaded. */
+ data class Loaded(
+ val key: String,
+ val isPrioritized: Boolean = false,
+ ) : SmartspaceMediaLoadingModel()
+
+ /** Smartspace media has been removed. */
+ data class Removed(
+ val key: String,
+ val immediatelyUpdateUi: Boolean = true,
+ ) : SmartspaceMediaLoadingModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 899b9ed103cd..ca898e675f53 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -118,8 +118,9 @@ import com.android.systemui.res.R;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.surfaceeffects.PaintDrawCallback;
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect;
-import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState;
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.AnimationState;
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView;
import com.android.systemui.surfaceeffects.ripple.MultiRippleController;
import com.android.systemui.surfaceeffects.ripple.MultiRippleView;
@@ -264,15 +265,15 @@ public class MediaControlPanel {
private boolean mWasPlaying = false;
private boolean mButtonClicked = false;
- private final LoadingEffect.Companion.PaintDrawCallback mNoiseDrawCallback =
- new LoadingEffect.Companion.PaintDrawCallback() {
+ private final PaintDrawCallback mNoiseDrawCallback =
+ new PaintDrawCallback() {
@Override
- public void onDraw(@NonNull Paint loadingPaint) {
- mMediaViewHolder.getLoadingEffectView().draw(loadingPaint);
+ public void onDraw(@NonNull Paint paint) {
+ mMediaViewHolder.getLoadingEffectView().draw(paint);
}
};
- private final LoadingEffect.Companion.AnimationStateChangedCallback mStateChangedCallback =
- new LoadingEffect.Companion.AnimationStateChangedCallback() {
+ private final LoadingEffect.AnimationStateChangedCallback mStateChangedCallback =
+ new LoadingEffect.AnimationStateChangedCallback() {
@Override
public void onStateChanged(@NonNull AnimationState oldState,
@NonNull AnimationState newState) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index adee7f2c86be..4db89d18ef7d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -1200,9 +1200,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
}
boolean isVolumeControlEnabled(@NonNull MediaDevice device) {
- return (isPlayBackInfoLocal()
- || device.getDeviceType() != MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE)
- && !device.isVolumeFixed();
+ return !device.isVolumeFixed();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index d2471225a093..f08bc17c4f23 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -25,8 +25,8 @@ import com.android.launcher3.icons.IconFactory
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
-import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
-import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.BasicAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.BasicPackageManagerAppIconLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
@@ -102,7 +102,7 @@ interface MediaProjectionAppSelectorModule {
@Binds
@MediaProjectionAppSelectorScope
- fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
+ fun bindAppIconLoader(impl: BasicPackageManagerAppIconLoader): BasicAppIconLoader
@Binds
@IntoSet
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt
new file mode 100644
index 000000000000..ca5b5f842b25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.mediaprojection.appselector.data
+
+import android.content.ComponentName
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import com.android.launcher3.icons.BaseIconFactory
+import com.android.launcher3.icons.IconFactory
+import com.android.launcher3.util.UserIconInfo
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+class BadgedAppIconLoader
+@Inject
+constructor(
+ private val basicAppIconLoader: BasicAppIconLoader,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val context: Context,
+ private val iconFactoryProvider: Provider<IconFactory>,
+) {
+
+ suspend fun loadIcon(
+ userId: Int,
+ userType: RecentTask.UserType,
+ componentName: ComponentName
+ ): Drawable? =
+ withContext(backgroundDispatcher) {
+ iconFactoryProvider.get().use<IconFactory, Drawable?> { iconFactory ->
+ val icon =
+ basicAppIconLoader.loadIcon(userId, componentName) ?: return@withContext null
+ val userHandler = UserHandle.of(userId)
+ val iconType = getIconType(userType)
+ val options =
+ BaseIconFactory.IconOptions().apply {
+ setUser(UserIconInfo(userHandler, iconType))
+ }
+ val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
+ badgedIcon.newIcon(context)
+ }
+ }
+
+ private fun getIconType(userType: RecentTask.UserType): Int =
+ when (userType) {
+ RecentTask.UserType.CLONED -> UserIconInfo.TYPE_CLONED
+ RecentTask.UserType.WORK -> UserIconInfo.TYPE_WORK
+ RecentTask.UserType.PRIVATE -> UserIconInfo.TYPE_PRIVATE
+ RecentTask.UserType.STANDARD -> UserIconInfo.TYPE_MAIN
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BasicAppIconLoader.kt
index b85d6285c35b..03f6f015300f 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BasicAppIconLoader.kt
@@ -17,45 +17,29 @@
package com.android.systemui.mediaprojection.appselector.data
import android.content.ComponentName
-import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
-import android.os.UserHandle
-import com.android.launcher3.icons.BaseIconFactory.IconOptions
-import com.android.launcher3.icons.IconFactory
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.shared.system.PackageManagerWrapper
import javax.inject.Inject
-import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
-interface AppIconLoader {
+interface BasicAppIconLoader {
suspend fun loadIcon(userId: Int, component: ComponentName): Drawable?
}
-class IconLoaderLibAppIconLoader
+class BasicPackageManagerAppIconLoader
@Inject
constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
- private val context: Context,
// Use wrapper to access hidden API that allows to get ActivityInfo for any user id
private val packageManagerWrapper: PackageManagerWrapper,
private val packageManager: PackageManager,
- private val iconFactoryProvider: Provider<IconFactory>
-) : AppIconLoader {
+) : BasicAppIconLoader {
override suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? =
withContext(backgroundDispatcher) {
- iconFactoryProvider.get().use<IconFactory, Drawable?> { iconFactory ->
- val activityInfo =
- packageManagerWrapper.getActivityInfo(component, userId)
- ?: return@withContext null
- val icon = activityInfo.loadIcon(packageManager) ?: return@withContext null
- val userHandler = UserHandle.of(userId)
- val options = IconOptions().apply { setUser(userHandler) }
- val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
- badgedIcon.newIcon(context)
- }
+ packageManagerWrapper.getActivityInfo(component, userId)?.loadIcon(packageManager)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index e9b458271ef7..2dbe2aa4ef2d 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -18,7 +18,9 @@ package com.android.systemui.mediaprojection.appselector.data
import android.annotation.ColorInt
import android.annotation.UserIdInt
+import android.app.ActivityManager.RecentTaskInfo
import android.content.ComponentName
+import com.android.wm.shell.util.SplitBounds
data class RecentTask(
val taskId: Int,
@@ -28,4 +30,30 @@ data class RecentTask(
val baseIntentComponent: ComponentName?,
@ColorInt val colorBackground: Int?,
val isForegroundTask: Boolean,
-)
+ val userType: UserType,
+ val splitBounds: SplitBounds?,
+) {
+ constructor(
+ taskInfo: RecentTaskInfo,
+ isForegroundTask: Boolean,
+ userType: UserType,
+ splitBounds: SplitBounds? = null
+ ) : this(
+ taskInfo.taskId,
+ taskInfo.displayId,
+ taskInfo.userId,
+ taskInfo.topActivity,
+ taskInfo.baseIntent?.component,
+ taskInfo.taskDescription?.backgroundColor,
+ isForegroundTask,
+ userType,
+ splitBounds
+ )
+
+ enum class UserType {
+ STANDARD,
+ WORK,
+ PRIVATE,
+ CLONED
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 5dde14bf0867..596c18f04134 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -17,6 +17,8 @@
package com.android.systemui.mediaprojection.appselector.data
import android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE
+import android.content.pm.UserInfo
+import android.os.UserManager
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.getOrNull
@@ -41,7 +43,8 @@ constructor(
@Background private val coroutineDispatcher: CoroutineDispatcher,
@Background private val backgroundExecutor: Executor,
private val recentTasks: Optional<RecentTasks>,
- private val userTracker: UserTracker
+ private val userTracker: UserTracker,
+ private val userManager: UserManager,
) : RecentTaskListProvider {
private val recents by lazy { recentTasks.getOrNull() }
@@ -55,19 +58,27 @@ constructor(
val foregroundTaskId1 = foregroundGroup?.taskInfo1?.taskId
val foregroundTaskId2 = foregroundGroup?.taskInfo2?.taskId
val foregroundTaskIds = listOfNotNull(foregroundTaskId1, foregroundTaskId2)
- groupedTasks
- .flatMap { listOfNotNull(it.taskInfo1, it.taskInfo2) }
- .map {
+ groupedTasks.flatMap {
+ val task1 =
RecentTask(
- it.taskId,
- it.displayId,
- it.userId,
- it.topActivity,
- it.baseIntent?.component,
- it.taskDescription?.backgroundColor,
- isForegroundTask = it.taskId in foregroundTaskIds && it.isVisible
+ it.taskInfo1,
+ it.taskInfo1.taskId in foregroundTaskIds && it.taskInfo1.isVisible,
+ userManager.getUserInfo(it.taskInfo1.userId).toUserType(),
+ it.splitBounds
)
- }
+
+ val task2 =
+ if (it.taskInfo2 != null) {
+ RecentTask(
+ it.taskInfo2!!,
+ it.taskInfo2!!.taskId in foregroundTaskIds && it.taskInfo2!!.isVisible,
+ userManager.getUserInfo(it.taskInfo2!!.userId).toUserType(),
+ it.splitBounds
+ )
+ } else null
+
+ listOfNotNull(task1, task2)
+ }
}
private suspend fun RecentTasks.getTasks(): List<GroupedRecentTaskInfo> =
@@ -81,4 +92,15 @@ constructor(
continuation.resume(tasks)
}
}
+
+ private fun UserInfo.toUserType(): RecentTask.UserType =
+ if (isCloneProfile) {
+ RecentTask.UserType.CLONED
+ } else if (isManagedProfile) {
+ RecentTask.UserType.WORK
+ } else if (isPrivateProfile) {
+ RecentTask.UserType.PRIVATE
+ } else {
+ RecentTask.UserType.STANDARD
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index 7c7efd0be8ed..9549ab1cab3e 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -24,9 +24,12 @@ import android.graphics.Rect
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.window.RemoteTransition
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.Flags.pssAppSelectorAbruptExitFix
+import com.android.systemui.Flags.pssAppSelectorRecentsSplitScreen
+import com.android.systemui.display.naturalBounds
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
import com.android.systemui.mediaprojection.appselector.data.RecentTask
@@ -34,6 +37,11 @@ import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.
import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
import com.android.systemui.res.R
import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.splitscreen.SplitScreen
+import com.android.wm.shell.util.SplitBounds
+import java.util.Optional
import javax.inject.Inject
/**
@@ -48,6 +56,7 @@ constructor(
private val taskViewSizeProvider: TaskPreviewSizeProvider,
private val activityTaskManager: IActivityTaskManager,
private val resultHandler: MediaProjectionAppSelectorResultHandler,
+ private val splitScreen: Optional<SplitScreen>,
) : RecentTaskClickListener, TaskPreviewSizeListener {
private var views: Views? = null
@@ -63,11 +72,11 @@ constructor(
fun createView(parent: ViewGroup): ViewGroup =
views?.root
?: createRecentViews(parent)
- .also {
- views = it
- lastBoundData?.let { recents -> bind(recents) }
- }
- .root
+ .also {
+ views = it
+ lastBoundData?.let { recents -> bind(recents) }
+ }
+ .root
fun bind(recentTasks: List<RecentTask>) {
views?.apply {
@@ -93,8 +102,10 @@ constructor(
private fun createRecentViews(parent: ViewGroup): Views {
val recentsRoot =
LayoutInflater.from(parent.context)
- .inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
- as ViewGroup
+ .inflate(R.layout.media_projection_recent_tasks,
+ parent, /* attachToRoot= */
+ false)
+ as ViewGroup
val container =
recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
@@ -121,18 +132,34 @@ constructor(
return Views(recentsRoot, container, progress, recycler)
}
+ private fun RecentTask.isLaunchingInSplitScreen(): Boolean {
+ return splitScreen.isPresent && splitBounds != null
+ }
+
override fun onRecentAppClicked(task: RecentTask, view: View) {
val launchCookie = LaunchCookie()
val activityOptions = createAnimation(task, view)
activityOptions.pendingIntentBackgroundActivityStartMode =
MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- activityOptions.setLaunchCookie(launchCookie)
activityOptions.launchDisplayId = task.displayId
+ activityOptions.setLaunchCookie(launchCookie)
+
+ val handleResult: () -> Unit = { resultHandler.returnSelectedApp(launchCookie)}
+
+ val taskId = task.taskId
+ val splitBounds = task.splitBounds
- activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
- resultHandler.returnSelectedApp(launchCookie)
+ if (pssAppSelectorRecentsSplitScreen() &&
+ task.isLaunchingInSplitScreen() &&
+ !task.isForegroundTask) {
+ startSplitScreenTask(view, taskId, splitBounds!!, handleResult, activityOptions)
+ } else {
+ activityTaskManager.startActivityFromRecents(taskId, activityOptions.toBundle())
+ handleResult()
+ }
}
+
private fun createAnimation(task: RecentTask, view: View): ActivityOptions =
if (pssAppSelectorAbruptExitFix() && task.isForegroundTask) {
// When the selected task is in the foreground, the scale up animation doesn't work.
@@ -145,7 +172,14 @@ constructor(
/* startedListener = */ null,
/* finishedListener = */ null
)
+ } else if (task.isLaunchingInSplitScreen()) {
+ // When the selected task isn't in the foreground, but is launching in split screen,
+ // then we don't need to specify an animation, since we'll already be passing a
+ // manually built remote animation to SplitScreenController
+ ActivityOptions.makeBasic()
} else {
+ // The default case is a selected task not in the foreground and launching fullscreen,
+ // so for this we can use the default ActivityOptions animation
ActivityOptions.makeScaleUpAnimation(
view,
/* startX= */ 0,
@@ -155,6 +189,29 @@ constructor(
)
}
+ private fun startSplitScreenTask(
+ view: View,
+ taskId: Int,
+ splitBounds: SplitBounds,
+ handleResult: () -> Unit,
+ activityOptions: ActivityOptions,
+ ) {
+ val isLeftTopTask = taskId == splitBounds.leftTopTaskId
+ val task2Id =
+ if (isLeftTopTask) splitBounds.rightBottomTaskId else splitBounds.leftTopTaskId
+ val splitPosition =
+ if (isLeftTopTask) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT
+
+ val animationRunner = RemoteRecentSplitTaskTransitionRunner(taskId, task2Id,
+ view.locationOnScreen, view.context.display.naturalBounds, handleResult)
+ val remoteTransition = RemoteTransition(animationRunner,
+ view.context.iApplicationThread, "startSplitScreenTask")
+
+ splitScreen.get().startTasks(taskId, activityOptions.toBundle(), task2Id, null,
+ splitPosition, splitBounds.snapPosition, remoteTransition, null)
+ }
+
+
override fun onTaskSizeChanged(size: Rect) {
views?.recentsContainer?.setTaskHeightSize()
}
@@ -163,12 +220,12 @@ constructor(
val thumbnailHeight = taskViewSizeProvider.size.height()
val itemHeight =
thumbnailHeight +
- context.resources.getDimensionPixelSize(
- R.dimen.media_projection_app_selector_task_icon_size
- ) +
- context.resources.getDimensionPixelSize(
- R.dimen.media_projection_app_selector_task_icon_margin
- ) * 2
+ context.resources.getDimensionPixelSize(
+ R.dimen.media_projection_app_selector_task_icon_size
+ ) +
+ context.resources.getDimensionPixelSize(
+ R.dimen.media_projection_app_selector_task_icon_margin
+ ) * 2
layoutParams = layoutParams.apply { height = itemHeight }
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
index 3fe040a0d715..3b84d2c53a2b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
@@ -21,12 +21,12 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import com.android.systemui.res.R
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelector
-import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.BadgedAppIconLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -39,7 +39,7 @@ class RecentTaskViewHolder
@AssistedInject
constructor(
@Assisted private val root: ViewGroup,
- private val iconLoader: AppIconLoader,
+ private val iconLoader: BadgedAppIconLoader,
private val thumbnailLoader: RecentTaskThumbnailLoader,
private val labelLoader: RecentTaskLabelLoader,
private val taskViewSizeProvider: TaskPreviewSizeProvider,
@@ -63,7 +63,7 @@ constructor(
scope.launch {
task.baseIntentComponent?.let { component ->
launch {
- val icon = iconLoader.loadIcon(task.userId, component)
+ val icon = iconLoader.loadIcon(task.userId, task.userType, component)
iconView.setImageDrawable(icon)
}
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt
new file mode 100644
index 000000000000..9514c4ab8f2d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.annotation.UiThread
+import android.graphics.Rect
+import android.os.IBinder
+import android.os.RemoteException
+import android.util.Log
+import android.view.SurfaceControl
+import android.view.animation.DecelerateInterpolator
+import android.window.IRemoteTransition
+import android.window.IRemoteTransitionFinishedCallback
+import android.window.TransitionInfo
+import android.window.WindowContainerToken
+import com.android.app.viewcapture.ViewCapture
+import com.android.internal.policy.TransitionAnimation
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity.Companion.TAG
+
+class RemoteRecentSplitTaskTransitionRunner(
+ private val firstTaskId: Int,
+ private val secondTaskId: Int,
+ private val viewPosition: IntArray,
+ private val screenBounds: Rect,
+ private val handleResult: () -> Unit,
+) : IRemoteTransition.Stub() {
+ override fun startAnimation(
+ transition: IBinder?,
+ info: TransitionInfo?,
+ t: SurfaceControl.Transaction?,
+ finishedCallback: IRemoteTransitionFinishedCallback
+ ) {
+ val launchAnimation = AnimatorSet()
+ var rootCandidate =
+ info!!.changes.firstOrNull {
+ it.taskInfo?.taskId == firstTaskId || it.taskInfo?.taskId == secondTaskId
+ }
+
+ // If we could not find a proper root candidate, something went wrong.
+ check(rootCandidate != null) { "Could not find a split root candidate" }
+
+ // Recurse up the tree until parent is null, then we've found our root.
+ var parentToken: WindowContainerToken? = rootCandidate.parent
+ while (parentToken != null) {
+ rootCandidate = info.getChange(parentToken) ?: break
+ parentToken = rootCandidate.parent
+ }
+
+ // Make sure nothing weird happened, like getChange() returning null.
+ check(rootCandidate != null) { "Failed to find a root leash" }
+
+ // Ending position is the full device screen.
+ val startingScale = 0.25f
+
+ val startX = viewPosition[0]
+ val startY = viewPosition[1]
+ val endX = screenBounds.left
+ val endY = screenBounds.top
+
+ ViewCapture.MAIN_EXECUTOR.execute {
+ val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
+ with(progressUpdater) {
+ interpolator = DecelerateInterpolator(1.5f)
+ setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION.toLong())
+
+ addUpdateListener { valueAnimator ->
+ val progress = valueAnimator.animatedFraction
+
+ val x = startX + ((endX - startX) * progress)
+ val y = startY + ((endY - startY) * progress)
+ val scale = startingScale + ((1 - startingScale) * progress)
+
+ t!!
+ .setPosition(rootCandidate.leash, x, y)
+ .setScale(rootCandidate.leash, scale, scale)
+ .setAlpha(rootCandidate.leash, progress)
+ .apply()
+ }
+
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ try {
+ onTransitionFinished()
+ finishedCallback.onTransitionFinished(null, null)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed to call transition finished callback", e)
+ }
+ }
+ }
+ )
+ }
+
+ launchAnimation.play(progressUpdater)
+ launchAnimation.start()
+ }
+ }
+
+ override fun mergeAnimation(
+ transition: IBinder?,
+ info: TransitionInfo?,
+ t: SurfaceControl.Transaction?,
+ mergeTarget: IBinder?,
+ finishedCallback: IRemoteTransitionFinishedCallback?
+ ) {}
+
+ @Throws(RemoteException::class)
+ override fun onTransitionConsumed(transition: IBinder, aborted: Boolean) {
+ Log.w(TAG, "unexpected consumption of app selector transition: aborted=$aborted")
+ }
+
+ @UiThread
+ private fun onTransitionFinished() {
+ // After finished transition, then invoke callback to close the app selector, so that
+ // finish animation of app selector does not override the launch animation of the split
+ // tasks
+ handleResult()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index b3d848c2d23b..30f33a3a2f6f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -34,7 +34,6 @@ import androidx.annotation.VisibleForTesting
import androidx.core.os.postDelayed
import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation
-import com.android.internal.jank.Cuj.CUJ_BACK_PANEL_ARROW
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.qualifiers.Main
@@ -1039,7 +1038,7 @@ internal constructor(
override fun dump(pw: PrintWriter) {
pw.println("$TAG:")
pw.println(" currentState=$currentState")
- pw.println(" isLeftPanel=$mView.isLeftPanel")
+ pw.println(" isLeftPanel=${mView.isLeftPanel}")
}
init {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
index 3b3844a4be7b..e4249757d737 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
@@ -31,7 +31,7 @@ import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
import com.android.systemui.res.R;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.util.LifecycleFragment;
import java.util.function.Consumer;
@@ -182,7 +182,7 @@ public class QSFragmentLegacy extends LifecycleFragment implements QS {
}
public void setBrightnessMirrorController(
- BrightnessMirrorController brightnessMirrorController) {
+ MirrorController brightnessMirrorController) {
if (mQsImpl != null) {
mQsImpl.setBrightnessMirrorController(brightnessMirrorController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index a000d63a2ee3..a0607e9f859a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -61,6 +61,7 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
@@ -68,7 +69,6 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.util.Utils;
@@ -544,7 +544,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
}
public void setBrightnessMirrorController(
- BrightnessMirrorController brightnessMirrorController) {
+ @Nullable MirrorController brightnessMirrorController) {
mQSPanelController.setBrightnessMirror(brightnessMirrorController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index cd6511979375..55dc4859cf90 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -24,6 +24,8 @@ import static com.android.systemui.qs.dagger.QSScopeModule.QS_USING_MEDIA_PLAYER
import android.view.MotionEvent;
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dump.DumpManager;
@@ -38,9 +40,9 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.tuner.TunerService;
@@ -139,6 +141,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
mBrightnessMirrorHandler.onQsPanelAttached();
PagedTileLayout pagedTileLayout= ((PagedTileLayout) mView.getOrCreateTileLayout());
pagedTileLayout.setOnTouchListener(mTileLayoutTouchListener);
+ maybeReinflateBrightnessSlider();
}
@Override
@@ -157,15 +160,18 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
@Override
protected void onConfigurationChanged() {
mView.updateResources();
+ maybeReinflateBrightnessSlider();
+ if (mView.isListening()) {
+ refreshAllTiles();
+ }
+ }
+
+ private void maybeReinflateBrightnessSlider() {
int newDensity = mView.getResources().getConfiguration().densityDpi;
if (newDensity != mLastDensity) {
mLastDensity = newDensity;
reinflateBrightnessSlider();
}
-
- if (mView.isListening()) {
- refreshAllTiles();
- }
}
private void reinflateBrightnessSlider() {
@@ -210,7 +216,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
}
}
- public void setBrightnessMirror(BrightnessMirrorController brightnessMirrorController) {
+ public void setBrightnessMirror(@Nullable MirrorController brightnessMirrorController) {
mBrightnessMirrorHandler.setController(brightnessMirrorController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index b0707db0d02d..6eae32a0ffd6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -39,6 +39,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
@@ -51,7 +52,6 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.BluetoothController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 8d5aeab54343..3d86e3c084f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -34,6 +34,7 @@ import com.android.systemui.qs.QSContainerImpl
import com.android.systemui.qs.QSImpl
import com.android.systemui.qs.dagger.QSSceneComponent
import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.MirrorController
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.util.kotlin.sample
@@ -68,6 +69,9 @@ interface QSSceneAdapter {
*/
val qsView: Flow<View>
+ /** Sets the [MirrorController] in [QSImpl]. Set to `null` to remove. */
+ fun setBrightnessMirrorController(mirrorController: MirrorController?)
+
/**
* Inflate an instance of [QSImpl] for this context. Once inflated, it will be available in
* [qsView]. Re-inflations due to configuration changes will use the last used [context].
@@ -206,7 +210,7 @@ constructor(
applicationScope.launch {
launch {
state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
- _qsImpl.value?.apply {
+ qsImpl.value?.apply {
if (state != QSSceneAdapter.State.QS && customizing) {
this@apply.closeCustomizerImmediately()
}
@@ -284,6 +288,10 @@ constructor(
qsImpl.value?.closeCustomizer()
}
+ override fun setBrightnessMirrorController(mirrorController: MirrorController?) {
+ qsImpl.value?.setBrightnessMirrorController(mirrorController)
+ }
+
private fun QSImpl.applyState(state: QSSceneAdapter.State) {
setQsVisible(state.isVisible)
setExpanded(state.isVisible && state.expansion > 0f)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 62ed49150eec..ab0b0b7b7c6c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -25,11 +25,15 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** Models UI state and handles user input for the quick settings scene. */
@@ -37,25 +41,27 @@ import kotlinx.coroutines.flow.map
class QuickSettingsSceneViewModel
@Inject
constructor(
+ val brightnessMirrorViewModel: BrightnessMirrorViewModel,
val shadeHeaderViewModel: ShadeHeaderViewModel,
val qsSceneAdapter: QSSceneAdapter,
val notifications: NotificationsPlaceholderViewModel,
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
+ private val sceneInteractor: SceneInteractor,
) {
val destinationScenes =
- qsSceneAdapter.isCustomizing.map { customizing ->
+ qsSceneAdapter.isCustomizing.flatMapLatest { customizing ->
if (customizing) {
- // TODO(b/332749288) Empty map so there are no back handlers and back can close
- // customizer
- emptyMap()
+ flowOf(emptyMap())
// TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
// while customizing
} else {
- mapOf(
- Back to UserActionResult(Scenes.Shade),
- Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
- )
+ sceneInteractor.previousScene.map { previousScene ->
+ mapOf(
+ Back to UserActionResult(previousScene ?: Scenes.Shade),
+ Swipe(SwipeDirection.Up) to UserActionResult(previousScene ?: Scenes.Shade),
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 5e4919d44f23..4d34a869002a 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -16,6 +16,7 @@
package com.android.systemui.recordissue
+import android.app.IActivityManager
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
@@ -51,7 +52,7 @@ class IssueRecordingService
@Inject
constructor(
controller: RecordingController,
- @LongRunning executor: Executor,
+ @LongRunning private val bgExecutor: Executor,
@Main handler: Handler,
uiEventLogger: UiEventLogger,
notificationManager: NotificationManager,
@@ -60,10 +61,12 @@ constructor(
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val panelInteractor: PanelInteractor,
private val issueRecordingState: IssueRecordingState,
+ private val iActivityManager: IActivityManager,
+ private val launcherApps: LauncherApps,
) :
RecordingService(
controller,
- executor,
+ bgExecutor,
handler,
uiEventLogger,
notificationManager,
@@ -103,12 +106,26 @@ constructor(
// ViewCapture needs to save it's data before it is disabled, or else the data will
// be lost. This is expected to change in the near future, and when that happens
// this line should be removed.
- getSystemService(LauncherApps::class.java)?.saveViewCaptureData()
+ launcherApps.saveViewCaptureData()
TraceUtils.traceStop(contentResolver)
issueRecordingState.isRecording = false
}
ACTION_SHARE -> {
- shareRecording(intent)
+ bgExecutor.execute {
+ mNotificationManager.cancelAsUser(
+ null,
+ mNotificationId,
+ UserHandle(mUserContextTracker.userContext.userId)
+ )
+
+ val screenRecording = intent.getParcelableExtra(EXTRA_PATH, Uri::class.java)
+ if (issueRecordingState.takeBugReport) {
+ iActivityManager.requestBugReportWithExtraAttachment(screenRecording)
+ } else {
+ shareRecording(screenRecording)
+ }
+ }
+
dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
panelInteractor.collapsePanels()
@@ -122,23 +139,17 @@ constructor(
return super.onStartCommand(intent, flags, startId)
}
- private fun shareRecording(intent: Intent) {
+ private fun shareRecording(screenRecording: Uri?) {
val sharableUri: Uri =
zipAndPackageRecordings(
TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get(),
- intent.getStringExtra(EXTRA_PATH)
+ screenRecording
)
?: return
val sendIntent =
FileSender.buildSendIntent(this, listOf(sharableUri))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- mNotificationManager.cancelAsUser(
- null,
- mNotificationId,
- UserHandle(mUserContextTracker.userContext.userId)
- )
-
// TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
mKeyguardDismissUtil.executeWhenUnlocked(
{
@@ -150,7 +161,7 @@ constructor(
)
}
- private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecordingUri: String?): Uri? {
+ private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecording: Uri?): Uri? {
try {
externalCacheDir?.mkdirs()
val outZip: File = File.createTempFile(TEMP_FILE_PREFIX, ZIP_SUFFIX, externalCacheDir)
@@ -160,8 +171,8 @@ constructor(
Files.copy(file.toPath(), os)
os.closeEntry()
}
- if (screenRecordingUri != null) {
- contentResolver.openInputStream(Uri.parse(screenRecordingUri))?.use {
+ if (screenRecording != null) {
+ contentResolver.openInputStream(screenRecording)?.use {
os.putNextEntry(ZipEntry(SCREEN_RECORDING_ZIP_LABEL))
it.transferTo(os)
os.closeEntry()
@@ -215,7 +226,7 @@ constructor(
fun getStartIntent(
context: Context,
screenRecord: Boolean,
- winscopeTracing: Boolean
+ winscopeTracing: Boolean,
): Intent =
Intent(context, IssueRecordingService::class.java)
.setAction(ACTION_START)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
index 394c5c2775a4..12ed06d75ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
@@ -25,6 +25,8 @@ class IssueRecordingState @Inject constructor() {
private val listeners = CopyOnWriteArrayList<Runnable>()
+ var takeBugReport: Boolean = false
+
var isRecording = false
set(value) {
field = value
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index dab61fa54908..68b88362427a 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -63,6 +63,7 @@ constructor(
private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
private val userFileManager: UserFileManager,
private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
+ private val issueRecordingState: IssueRecordingState,
@Assisted private val onStarted: Consumer<IssueRecordingConfig>,
) : SystemUIDialog.Delegate {
@@ -74,6 +75,7 @@ constructor(
}
@SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
+ @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var bugReportSwitch: Switch
private lateinit var issueTypeButton: Button
@MainThread
@@ -86,6 +88,7 @@ constructor(
setPositiveButton(
R.string.qs_record_issue_start,
{ _, _ ->
+ issueRecordingState.takeBugReport = bugReportSwitch.isChecked
onStarted.accept(
IssueRecordingConfig(
screenRecordSwitch.isChecked,
@@ -113,6 +116,7 @@ constructor(
bgExecutor.execute { onScreenRecordSwitchClicked() }
}
}
+ bugReportSwitch = requireViewById(R.id.bugreport_switch)
val startButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
issueTypeButton = requireViewById(R.id.issue_type_button)
issueTypeButton.setOnClickListener {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 994b01216c22..3082eb904a51 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -24,6 +24,8 @@ import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSource
+import com.android.systemui.util.kotlin.WithPrev
+import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,6 +36,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Source of truth for scene framework application state. */
@@ -44,7 +47,32 @@ constructor(
private val config: SceneContainerConfig,
private val dataSource: SceneDataSource,
) {
- val currentScene: StateFlow<SceneKey> = dataSource.currentScene
+ private val previousAndCurrentScene: StateFlow<WithPrev<SceneKey?, SceneKey>> =
+ dataSource.currentScene
+ .pairwise()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = WithPrev(null, dataSource.currentScene.value),
+ )
+
+ val currentScene: StateFlow<SceneKey> =
+ previousAndCurrentScene
+ .map { it.newValue }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = previousAndCurrentScene.value.newValue,
+ )
+
+ val previousScene: StateFlow<SceneKey?> =
+ previousAndCurrentScene
+ .map { it.previousValue }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = previousAndCurrentScene.value.previousValue,
+ )
private val _isVisible = MutableStateFlow(true)
val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 2ccd3b9e9f8a..02394552e8f9 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -140,6 +140,14 @@ constructor(
)
/**
+ * The previous scene.
+ *
+ * This is effectively the previous value of [currentScene] which means that all caveats, for
+ * example regarding when in a transition the current scene changes, apply.
+ */
+ val previousScene: StateFlow<SceneKey?> = repository.previousScene
+
+ /**
* Returns the keys of all scenes in the container.
*
* The scenes will be sorted in z-order such that the last one is the one that should be
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 32d72e0bac22..0e66c28d4b8d 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
@@ -47,6 +47,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -61,6 +62,7 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -68,10 +70,12 @@ import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/**
@@ -103,6 +107,7 @@ constructor(
private val headsUpInteractor: HeadsUpNotificationInteractor,
private val occlusionInteractor: SceneContainerOcclusionInteractor,
private val faceUnlockInteractor: DeviceEntryFaceAuthInteractor,
+ private val shadeInteractor: ShadeInteractor,
) : CoreStartable {
override fun start() {
@@ -185,6 +190,14 @@ constructor(
/** Switches between scenes based on ever-changing application state. */
private fun automaticallySwitchScenes() {
+ handleBouncerImeVisibility()
+ handleSimUnlock()
+ handleDeviceUnlockStatus()
+ handlePowerState()
+ handleShadeTouchability()
+ }
+
+ private fun handleBouncerImeVisibility() {
applicationScope.launch {
// TODO (b/308001302): Move this to a bouncer specific interactor.
bouncerInteractor.onImeHiddenByUser.collectLatest {
@@ -196,6 +209,9 @@ constructor(
}
}
}
+ }
+
+ private fun handleSimUnlock() {
applicationScope.launch {
simBouncerInteractor
.get()
@@ -229,7 +245,16 @@ constructor(
}
}
}
+ }
+
+ private fun handleDeviceUnlockStatus() {
applicationScope.launch {
+ // Track the previous scene (sans Bouncer), so that we know where to go when the device
+ // is unlocked whilst on the bouncer.
+ val previousScene =
+ sceneInteractor.previousScene
+ .filterNot { it == Scenes.Bouncer }
+ .stateIn(this, SharingStarted.Eagerly, initialValue = null)
deviceUnlockedInteractor.deviceUnlockStatus
.mapNotNull { deviceUnlockStatus ->
val renderedScenes =
@@ -257,8 +282,15 @@ constructor(
when {
isOnBouncer ->
- // When the device becomes unlocked in Bouncer, go to Gone.
- Scenes.Gone to "device was unlocked in Bouncer scene"
+ // When the device becomes unlocked in Bouncer, go to previous scene,
+ // or Gone.
+ if (previousScene.value == Scenes.Lockscreen) {
+ Scenes.Gone to "device was unlocked in Bouncer scene"
+ } else {
+ val prevScene = previousScene.value
+ (prevScene ?: Scenes.Gone) to
+ "device was unlocked in Bouncer scene, from sceneKey=$prevScene"
+ }
isOnLockscreen ->
// The lockscreen should be dismissed automatically in 2 scenarios:
// 1. When face auth bypass is enabled and authentication happens while
@@ -288,7 +320,9 @@ constructor(
)
}
}
+ }
+ private fun handlePowerState() {
applicationScope.launch {
powerInteractor.isAsleep.collect { isAsleep ->
if (isAsleep) {
@@ -317,7 +351,7 @@ constructor(
) {
switchToScene(
targetSceneKey = Scenes.Bouncer,
- loggingReason = "device is starting to wake up with a locked sim"
+ loggingReason = "device is starting to wake up with a locked sim",
)
}
}
@@ -325,6 +359,20 @@ constructor(
}
}
+ private fun handleShadeTouchability() {
+ applicationScope.launch {
+ shadeInteractor.isShadeTouchable
+ .distinctUntilChanged()
+ .filter { !it }
+ .collect {
+ switchToScene(
+ targetSceneKey = Scenes.Lockscreen,
+ loggingReason = "device became non-interactive",
+ )
+ }
+ }
+ }
+
/** Keeps [SysUiState] up-to-date */
private fun hydrateSystemUiState() {
applicationScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index c9291966bc15..cff11a753fe2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -21,12 +21,14 @@ package com.android.systemui.scene.shared.flag
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.Flags.sceneContainer
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.ComposeLockscreen
+import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag
@@ -48,7 +50,9 @@ object SceneContainerFlag {
MediaInSceneContainerFlag.isEnabled &&
MigrateClocksToBlueprint.isEnabled &&
NotificationsHeadsUpRefactor.isEnabled &&
- PredictiveBackSysUiFlag.isEnabled
+ PredictiveBackSysUiFlag.isEnabled &&
+ DeviceEntryUdfpsRefactor.isEnabled &&
+ RefactorKeyguardDismissIntent.isEnabled
// NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
/** The main aconfig flag. */
@@ -64,6 +68,8 @@ object SceneContainerFlag {
MigrateClocksToBlueprint.token,
NotificationsHeadsUpRefactor.token,
PredictiveBackSysUiFlag.token,
+ DeviceEntryUdfpsRefactor.token,
+ RefactorKeyguardDismissIntent.token,
// NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index b2c01e180ec4..cbb61b37b7a4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -206,7 +206,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
break;
case ACTION_SHARE:
- Uri shareUri = Uri.parse(intent.getStringExtra(EXTRA_PATH));
+ Uri shareUri = intent.getParcelableExtra(EXTRA_PATH, Uri.class);
Intent shareIntent = new Intent(Intent.ACTION_SEND)
.setType("video/mp4")
@@ -356,7 +356,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
PendingIntent.getService(
this,
REQUEST_CODE,
- getShareIntent(this, uri != null ? uri.toString() : null),
+ getShareIntent(this, uri),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
.build();
@@ -512,7 +512,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF);
}
- private Intent getShareIntent(Context context, String path) {
+ private Intent getShareIntent(Context context, Uri path) {
return new Intent(context, this.getClass()).setAction(ACTION_SHARE)
.putExtra(EXTRA_PATH, path);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
new file mode 100644
index 000000000000..caa67dff086f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.app.ActivityOptions
+import android.app.BroadcastOptions
+import android.app.ExitTransitionCoordinator
+import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.UserHandle
+import android.util.Log
+import android.util.Pair
+import android.view.View
+import android.view.Window
+import com.android.app.tracing.coroutines.launch
+import com.android.internal.app.ChooserActivity
+import com.android.systemui.dagger.qualifiers.Application
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+
+class ActionExecutor
+@AssistedInject
+constructor(
+ private val intentExecutor: ActionIntentExecutor,
+ @Application private val applicationScope: CoroutineScope,
+ @Assisted val window: Window,
+ @Assisted val transitionView: View,
+ @Assisted val onDismiss: (() -> Unit)
+) {
+
+ var isPendingSharedTransition = false
+ private set
+
+ fun startSharedTransition(intent: Intent, user: UserHandle, overrideTransition: Boolean) {
+ isPendingSharedTransition = true
+ val windowTransition = createWindowTransition()
+ applicationScope.launch("$TAG#launchIntentAsync") {
+ intentExecutor.launchIntent(
+ intent,
+ user,
+ overrideTransition,
+ windowTransition.first,
+ windowTransition.second
+ )
+ }
+ }
+
+ fun sendPendingIntent(pendingIntent: PendingIntent) {
+ try {
+ val options = BroadcastOptions.makeBasic()
+ options.setInteractive(true)
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ pendingIntent.send(options.toBundle())
+ onDismiss.invoke()
+ } catch (e: PendingIntent.CanceledException) {
+ Log.e(TAG, "Intent cancelled", e)
+ }
+ }
+
+ /**
+ * Supplies the necessary bits for the shared element transition to share sheet. Note that once
+ * called, the action intent to share must be sent immediately after.
+ */
+ private fun createWindowTransition(): Pair<ActivityOptions, ExitTransitionCoordinator> {
+ val callbacks: ExitTransitionCallbacks =
+ object : ExitTransitionCallbacks {
+ override fun isReturnTransitionAllowed(): Boolean {
+ return false
+ }
+
+ override fun hideSharedElements() {
+ isPendingSharedTransition = false
+ onDismiss.invoke()
+ }
+
+ override fun onFinish() {}
+ }
+ return ActivityOptions.startSharedElementAnimation(
+ window,
+ callbacks,
+ null,
+ Pair.create(transitionView, ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)
+ )
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(window: Window, transitionView: View, onDismiss: (() -> Unit)): ActionExecutor
+ }
+
+ companion object {
+ private const val TAG = "ActionExecutor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index 8e9769ab7d0e..a0cef529ecde 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -23,7 +23,9 @@ import android.content.ContentProvider
import android.content.Context
import android.content.Intent
import android.net.Uri
+import android.os.UserHandle
import com.android.systemui.res.R
+import com.android.systemui.screenshot.scroll.LongScreenshotActivity
object ActionIntentCreator {
/** @return a chooser intent to share the given URI. */
@@ -89,6 +91,14 @@ object ActionIntentCreator {
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
+ /** @return an Intent to start the LongScreenshotActivity */
+ fun createLongScreenshotIntent(owner: UserHandle, context: Context): Intent {
+ return Intent(context, LongScreenshotActivity::class.java)
+ .putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE, owner)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ }
+
private const val EXTRA_EDIT_SOURCE = "edit_source"
private const val EDIT_SOURCE_SCREENSHOT = "screenshot"
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 1f9853b17a28..4eca51d47a36 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -25,7 +25,6 @@ import android.os.Process.myUserHandle
import android.os.RemoteException
import android.os.UserHandle
import android.util.Log
-import android.util.Pair
import android.view.IRemoteAnimationFinishedCallback
import android.view.IRemoteAnimationRunner
import android.view.RemoteAnimationAdapter
@@ -67,20 +66,22 @@ constructor(
*/
fun launchIntentAsync(
intent: Intent,
- transition: Pair<ActivityOptions, ExitTransitionCoordinator>?,
user: UserHandle,
overrideTransition: Boolean,
+ options: ActivityOptions?,
+ transitionCoordinator: ExitTransitionCoordinator?,
) {
applicationScope.launch("$TAG#launchIntentAsync") {
- launchIntent(intent, transition, user, overrideTransition)
+ launchIntent(intent, user, overrideTransition, options, transitionCoordinator)
}
}
suspend fun launchIntent(
intent: Intent,
- transition: Pair<ActivityOptions, ExitTransitionCoordinator>?,
user: UserHandle,
overrideTransition: Boolean,
+ options: ActivityOptions?,
+ transitionCoordinator: ExitTransitionCoordinator?,
) {
if (screenshotActionDismissSystemWindows()) {
keyguardController.dismiss()
@@ -90,14 +91,12 @@ constructor(
} else {
dismissKeyguard()
}
- transition?.second?.startExit()
+ transitionCoordinator?.startExit()
if (user == myUserHandle()) {
- withContext(mainDispatcher) {
- context.startActivity(intent, transition?.first?.toBundle())
- }
+ withContext(mainDispatcher) { context.startActivity(intent, options?.toBundle()) }
} else {
- launchCrossProfileIntent(user, intent, transition?.first?.toBundle())
+ launchCrossProfileIntent(user, intent, options?.toBundle())
}
if (overrideTransition) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java b/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java
index ab8fc652c938..12bff499217e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.screenshot;
+import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.app.IAssistDataReceiver;
@@ -55,7 +56,7 @@ public class AssistContentRequester {
* Called when the {@link android.app.assist.AssistContent} of the requested task is
* available.
**/
- void onAssistContentAvailable(AssistContent assistContent);
+ void onAssistContentAvailable(@Nullable AssistContent assistContent);
}
private final IActivityTaskManager mActivityTaskManager;
@@ -117,15 +118,9 @@ public class AssistContentRequester {
@Override
public void onHandleAssistData(Bundle data) {
- if (data == null) {
- return;
- }
-
- final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT);
- if (content == null) {
- Log.e(TAG, "Received AssistData, but no AssistContent found");
- return;
- }
+ final AssistContent content = (data == null) ? null
+ : data.getParcelable(
+ ASSIST_KEY_CONTENT, AssistContent.class);
AssistContentRequester requester = mParentRef.get();
if (requester != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java
index 6224e1bf2414..afb0280a3917 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.screenshot;
+import dagger.Binds;
import dagger.Module;
import dagger.Provides;
@@ -29,4 +30,9 @@ public interface ReferenceScreenshotModule {
static ScreenshotNotificationSmartActionsProvider providesScrnshtNotifSmartActionsProvider() {
return new ScreenshotNotificationSmartActionsProvider();
}
+
+ /** */
+ @Binds
+ ScreenshotActionsProvider.Factory bindScreenshotActionsProviderFactory(
+ DefaultScreenshotActionsProvider.Factory defaultScreenshotActionsProviderFactory);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
index 60c44309fb07..07e143a34319 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
@@ -16,57 +16,43 @@
package com.android.systemui.screenshot
-import android.app.ActivityOptions
-import android.app.BroadcastOptions
-import android.app.ExitTransitionCoordinator
-import android.app.PendingIntent
import android.app.assist.AssistContent
import android.content.Context
-import android.content.Intent
-import android.os.Process
-import android.os.UserHandle
-import android.provider.DeviceConfig
import android.util.Log
-import android.util.Pair
import androidx.appcompat.content.res.AppCompatResources
-import com.android.app.tracing.coroutines.launch
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
import com.android.systemui.screenshot.ActionIntentCreator.createEdit
import com.android.systemui.screenshot.ActionIntentCreator.createShareWithSubject
-import com.android.systemui.screenshot.ScreenshotController.SavedImageData
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED
-import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED
-import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
+import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import java.text.DateFormat
-import java.util.Date
-import kotlinx.coroutines.CoroutineScope
/**
* Provides actions for screenshots. This class can be overridden by a vendor-specific SysUI
* implementation.
*/
interface ScreenshotActionsProvider {
- fun setCompletedScreenshot(result: SavedImageData)
- fun isPendingSharedTransition(): Boolean
+ fun onScrollChipReady(onClick: Runnable)
+ fun setCompletedScreenshot(result: ScreenshotSavedResult)
- fun onAssistContentAvailable(assistContent: AssistContent) {}
+ /**
+ * Provide the AssistContent for the focused task if available, null if the focused task isn't
+ * known or didn't return data.
+ */
+ fun onAssistContent(assistContent: AssistContent?) {}
interface Factory {
fun create(
request: ScreenshotData,
requestId: String,
- windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
- requestDismissal: () -> Unit,
+ actionExecutor: ActionExecutor,
): ScreenshotActionsProvider
}
}
@@ -76,182 +62,98 @@ class DefaultScreenshotActionsProvider
constructor(
private val context: Context,
private val viewModel: ScreenshotViewModel,
- private val actionExecutor: ActionIntentExecutor,
- private val smartActionsProvider: SmartActionsProvider,
private val uiEventLogger: UiEventLogger,
- @Application private val applicationScope: CoroutineScope,
@Assisted val request: ScreenshotData,
@Assisted val requestId: String,
- @Assisted val windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
- @Assisted val requestDismissal: () -> Unit,
+ @Assisted val actionExecutor: ActionExecutor,
) : ScreenshotActionsProvider {
- private var pendingAction: ((SavedImageData) -> Unit)? = null
- private var result: SavedImageData? = null
- private var isPendingSharedTransition = false
+ private var pendingAction: ((ScreenshotSavedResult) -> Unit)? = null
+ private var result: ScreenshotSavedResult? = null
init {
viewModel.setPreviewAction {
debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" }
uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString)
onDeferrableActionTapped { result ->
- startSharedTransition(createEdit(result.uri, context), true)
+ actionExecutor.startSharedTransition(
+ createEdit(result.uri, context),
+ result.user,
+ true
+ )
}
}
viewModel.addAction(
- ActionButtonViewModel(
+ ActionButtonAppearance(
AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_edit),
context.resources.getString(R.string.screenshot_edit_label),
context.resources.getString(R.string.screenshot_edit_description),
- ) {
- debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
- uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString)
- onDeferrableActionTapped { result ->
- startSharedTransition(createEdit(result.uri, context), true)
- }
+ )
+ ) {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
+ uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString)
+ onDeferrableActionTapped { result ->
+ actionExecutor.startSharedTransition(
+ createEdit(result.uri, context),
+ result.user,
+ true
+ )
}
- )
+ }
+
viewModel.addAction(
- ActionButtonViewModel(
+ ActionButtonAppearance(
AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_share),
context.resources.getString(R.string.screenshot_share_label),
context.resources.getString(R.string.screenshot_share_description),
- ) {
- debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" }
- uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString)
- onDeferrableActionTapped { result ->
- startSharedTransition(createShareWithSubject(result.uri, result.subject), false)
- }
- }
- )
- if (smartActionsEnabled(request.userHandle ?: Process.myUserHandle())) {
- smartActionsProvider.requestQuickShare(request, requestId) { quickShare ->
- if (!quickShare.actionIntent.isImmutable) {
- viewModel.addAction(
- ActionButtonViewModel(
- quickShare.getIcon().loadDrawable(context),
- quickShare.title,
- quickShare.title,
- ) {
- debugLog(LogConfig.DEBUG_ACTIONS) { "Quickshare tapped" }
- onDeferrableActionTapped { result ->
- uiEventLogger.log(
- SCREENSHOT_SMART_ACTION_TAPPED,
- 0,
- request.packageNameString
- )
- sendPendingIntent(
- smartActionsProvider
- .wrapIntent(
- quickShare,
- result.uri,
- result.subject,
- requestId
- )
- .actionIntent
- )
- }
- }
- )
- } else {
- Log.w(TAG, "Received immutable quick share pending intent; ignoring")
- }
+ )
+ ) {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" }
+ uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString)
+ onDeferrableActionTapped { result ->
+ actionExecutor.startSharedTransition(
+ createShareWithSubject(result.uri, result.subject),
+ result.user,
+ false
+ )
}
}
}
- override fun setCompletedScreenshot(result: SavedImageData) {
+ override fun onScrollChipReady(onClick: Runnable) {
+ viewModel.addAction(
+ ActionButtonAppearance(
+ AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll),
+ context.resources.getString(R.string.screenshot_scroll_label),
+ context.resources.getString(R.string.screenshot_scroll_label),
+ )
+ ) {
+ onClick.run()
+ }
+ }
+
+ override fun setCompletedScreenshot(result: ScreenshotSavedResult) {
if (this.result != null) {
Log.e(TAG, "Got a second completed screenshot for existing request!")
return
}
- if (result.uri == null || result.owner == null || result.imageTime == null) {
- Log.e(TAG, "Invalid result provided!")
- return
- }
- if (result.subject == null) {
- result.subject = getSubjectString(result.imageTime)
- }
this.result = result
pendingAction?.invoke(result)
- if (smartActionsEnabled(result.owner)) {
- smartActionsProvider.requestSmartActions(request, requestId, result) { smartActions ->
- viewModel.addActions(
- smartActions.map {
- ActionButtonViewModel(
- it.getIcon().loadDrawable(context),
- it.title,
- it.title,
- ) {
- sendPendingIntent(it.actionIntent)
- }
- }
- )
- }
- }
- }
-
- override fun isPendingSharedTransition(): Boolean {
- return isPendingSharedTransition
}
- private fun onDeferrableActionTapped(onResult: (SavedImageData) -> Unit) {
+ private fun onDeferrableActionTapped(onResult: (ScreenshotSavedResult) -> Unit) {
result?.let { onResult.invoke(it) } ?: run { pendingAction = onResult }
}
- private fun startSharedTransition(intent: Intent, overrideTransition: Boolean) {
- val user =
- result?.owner
- ?: run {
- Log.wtf(TAG, "User handle not provided in screenshot result! Result: $result")
- return
- }
- isPendingSharedTransition = true
- applicationScope.launch("$TAG#launchIntentAsync") {
- actionExecutor.launchIntent(intent, windowTransition.invoke(), user, overrideTransition)
- }
- }
-
- private fun sendPendingIntent(pendingIntent: PendingIntent) {
- try {
- val options = BroadcastOptions.makeBasic()
- options.setInteractive(true)
- options.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- )
- pendingIntent.send(options.toBundle())
- requestDismissal.invoke()
- } catch (e: PendingIntent.CanceledException) {
- Log.e(TAG, "Intent cancelled", e)
- }
- }
-
- private fun smartActionsEnabled(user: UserHandle): Boolean {
- val savingToOtherUser = user != Process.myUserHandle()
- return !savingToOtherUser &&
- DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS,
- true
- )
- }
-
- private fun getSubjectString(imageTime: Long): String {
- val subjectDate = DateFormat.getDateTimeInstance().format(Date(imageTime))
- return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate)
- }
-
@AssistedFactory
interface Factory : ScreenshotActionsProvider.Factory {
override fun create(
request: ScreenshotData,
requestId: String,
- windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
- requestDismissal: () -> Unit,
+ actionExecutor: ActionExecutor,
): DefaultScreenshotActionsProvider
}
companion object {
private const val TAG = "ScreenshotActionsProvider"
- private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 1e513b200ac4..6871084aa938 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -34,7 +34,6 @@ import android.animation.AnimatorListenerAdapter;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ExitTransitionCoordinator;
import android.app.ICompatCameraControlCallback;
@@ -51,7 +50,6 @@ import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Process;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -59,17 +57,12 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
import android.view.ScrollCaptureResponse;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
import android.widget.Toast;
import android.window.WindowContext;
@@ -84,10 +77,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.res.R;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
-import com.android.systemui.screenshot.scroll.LongScreenshotActivity;
-import com.android.systemui.screenshot.scroll.LongScreenshotData;
-import com.android.systemui.screenshot.scroll.ScrollCaptureClient;
-import com.android.systemui.screenshot.scroll.ScrollCaptureController;
+import com.android.systemui.screenshot.scroll.ScrollCaptureExecutor;
import com.android.systemui.util.Assert;
import com.google.common.util.concurrent.ListenableFuture;
@@ -100,12 +90,9 @@ import kotlin.Unit;
import java.util.List;
import java.util.UUID;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
import java.util.function.Consumer;
import javax.inject.Provider;
@@ -117,34 +104,6 @@ import javax.inject.Provider;
public class ScreenshotController {
private static final String TAG = logTag(ScreenshotController.class);
- private ScrollCaptureResponse mLastScrollCaptureResponse;
- private ListenableFuture<ScrollCaptureResponse> mLastScrollCaptureRequest;
-
- /**
- * This is effectively a no-op, but we need something non-null to pass in, in order to
- * successfully override the pending activity entrance animation.
- */
- static final IRemoteAnimationRunner.Stub SCREENSHOT_REMOTE_RUNNER =
- new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(
- @WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- final IRemoteAnimationFinishedCallback finishedCallback) {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Log.e(TAG, "Error finishing screenshot remote animation", e);
- }
- }
-
- @Override
- public void onAnimationCancelled() {
- }
- };
-
/**
* POD used in the AsyncTask which saves an image in the background.
*/
@@ -217,11 +176,12 @@ public class ScreenshotController {
// These strings are used for communicating the action invoked to
// ScreenshotNotificationSmartActionsProvider.
- static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
- static final String EXTRA_ID = "android:screenshot_id";
- static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
- static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
- static final String EXTRA_ACTION_INTENT_FILLIN = "android:screenshot_action_intent_fillin";
+ public static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
+ public static final String EXTRA_ID = "android:screenshot_id";
+ public static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
+ public static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
+ public static final String EXTRA_ACTION_INTENT_FILLIN =
+ "android:screenshot_action_intent_fillin";
// From WizardManagerHelper.java
@@ -242,22 +202,20 @@ public class ScreenshotController {
private final ExecutorService mBgExecutor;
private final BroadcastSender mBroadcastSender;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final ActionExecutor mActionExecutor;
private final WindowManager mWindowManager;
private final WindowManager.LayoutParams mWindowLayoutParams;
@Nullable
private final ScreenshotSoundController mScreenshotSoundController;
- private final ScrollCaptureClient mScrollCaptureClient;
private final PhoneWindow mWindow;
private final DisplayManager mDisplayManager;
private final int mDisplayId;
- private final ScrollCaptureController mScrollCaptureController;
- private final LongScreenshotData mLongScreenshotHolder;
- private final boolean mIsLowRamDevice;
+ private final ScrollCaptureExecutor mScrollCaptureExecutor;
private final ScreenshotNotificationSmartActionsProvider
mScreenshotNotificationSmartActionsProvider;
private final TimeoutHandler mScreenshotHandler;
- private final ActionIntentExecutor mActionExecutor;
+ private final ActionIntentExecutor mActionIntentExecutor;
private final UserManager mUserManager;
private final AssistContentRequester mAssistContentRequester;
@@ -299,19 +257,17 @@ public class ScreenshotController {
ScreenshotActionsProvider.Factory actionsProviderFactory,
ScreenshotSmartActions screenshotSmartActions,
ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
- ScrollCaptureClient scrollCaptureClient,
UiEventLogger uiEventLogger,
ImageExporter imageExporter,
ImageCapture imageCapture,
@Main Executor mainExecutor,
- ScrollCaptureController scrollCaptureController,
- LongScreenshotData longScreenshotHolder,
- ActivityManager activityManager,
+ ScrollCaptureExecutor scrollCaptureExecutor,
TimeoutHandler timeoutHandler,
BroadcastSender broadcastSender,
BroadcastDispatcher broadcastDispatcher,
ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
- ActionIntentExecutor actionExecutor,
+ ActionIntentExecutor actionIntentExecutor,
+ ActionExecutor.Factory actionExecutorFactory,
UserManager userManager,
AssistContentRequester assistContentRequester,
MessageContainerController messageContainerController,
@@ -322,14 +278,11 @@ public class ScreenshotController {
mScreenshotSmartActions = screenshotSmartActions;
mActionsProviderFactory = actionsProviderFactory;
mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);
- mScrollCaptureClient = scrollCaptureClient;
mUiEventLogger = uiEventLogger;
mImageExporter = imageExporter;
mImageCapture = imageCapture;
mMainExecutor = mainExecutor;
- mScrollCaptureController = scrollCaptureController;
- mLongScreenshotHolder = longScreenshotHolder;
- mIsLowRamDevice = activityManager.isLowRamDevice();
+ mScrollCaptureExecutor = scrollCaptureExecutor;
mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider;
mBgExecutor = Executors.newSingleThreadExecutor();
mBroadcastSender = broadcastSender;
@@ -345,7 +298,7 @@ public class ScreenshotController {
final Context displayContext = context.createDisplayContext(getDisplay());
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mFlags = flags;
- mActionExecutor = actionExecutor;
+ mActionIntentExecutor = actionIntentExecutor;
mUserManager = userManager;
mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
@@ -369,6 +322,12 @@ public class ScreenshotController {
mConfigChanges.applyNewConfig(context.getResources());
reloadAssets();
+ mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy.getScreenshotPreview(),
+ () -> {
+ requestDismissal(null);
+ return Unit.INSTANCE;
+ });
+
// Sound is only reproduced from the controller of the default display.
if (displayId == Display.DEFAULT_DISPLAY) {
mScreenshotSoundController = screenshotSoundController.get();
@@ -395,10 +354,10 @@ public class ScreenshotController {
Assert.isMainThread();
mCurrentRequestCallback = requestCallback;
- if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) {
+ if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+ && screenshot.getBitmap() == null) {
Rect bounds = getFullScreenRect();
- screenshot.setBitmap(
- mImageCapture.captureDisplay(mDisplayId, bounds));
+ screenshot.setBitmap(mImageCapture.captureDisplay(mDisplayId, bounds));
screenshot.setScreenBounds(bounds);
}
@@ -447,18 +406,15 @@ public class ScreenshotController {
if (screenshotShelfUi()) {
final UUID requestId = UUID.randomUUID();
final String screenshotId = String.format("Screenshot_%s", requestId);
- mActionsProvider = mActionsProviderFactory.create(screenshot, screenshotId,
- this::createWindowTransition, () -> {
- mViewProxy.requestDismissal(null);
- return Unit.INSTANCE;
- });
+ mActionsProvider = mActionsProviderFactory.create(
+ screenshot, screenshotId, mActionExecutor);
saveScreenshotInBackground(screenshot, requestId, finisher);
if (screenshot.getTaskId() >= 0) {
mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
- assistContent -> {
- mActionsProvider.onAssistContentAvailable(assistContent);
- });
+ assistContent -> mActionsProvider.onAssistContent(assistContent));
+ } else {
+ mActionsProvider.onAssistContent(null);
}
} else {
saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
@@ -548,7 +504,7 @@ public class ScreenshotController {
boolean isPendingSharedTransition() {
if (screenshotShelfUi()) {
- return mActionsProvider != null && mActionsProvider.isPendingSharedTransition();
+ return mActionExecutor.isPendingSharedTransition();
} else {
return mViewProxy.isPendingSharedTransition();
}
@@ -599,8 +555,9 @@ public class ScreenshotController {
@Override
public void onAction(Intent intent, UserHandle owner, boolean overrideTransition) {
- mActionExecutor.launchIntentAsync(
- intent, createWindowTransition(), owner, overrideTransition);
+ Pair<ActivityOptions, ExitTransitionCoordinator> exit = createWindowTransition();
+ mActionIntentExecutor.launchIntentAsync(
+ intent, owner, overrideTransition, exit.first, exit.second);
}
@Override
@@ -637,9 +594,8 @@ public class ScreenshotController {
mViewProxy.hideScrollChip();
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
- mScreenshotHandler.postDelayed(() -> {
- requestScrollCapture(owner);
- }, 150);
+ mScreenshotHandler.postDelayed(
+ () -> requestScrollCapture(owner), 150);
mViewProxy.updateInsets(
mWindowManager.getCurrentWindowMetrics().getWindowInsets());
// Screenshot animation calculations won't be valid anymore,
@@ -662,119 +618,51 @@ public class ScreenshotController {
}
private void requestScrollCapture(UserHandle owner) {
- if (!allowLongScreenshots()) {
- Log.d(TAG, "Long screenshots not supported on this device");
- return;
- }
- mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken());
- if (mLastScrollCaptureRequest != null) {
- mLastScrollCaptureRequest.cancel(true);
- }
- final ListenableFuture<ScrollCaptureResponse> future = mScrollCaptureClient.request(
- mDisplayId);
- mLastScrollCaptureRequest = future;
- mLastScrollCaptureRequest.addListener(() ->
- onScrollCaptureResponseReady(future, owner), mMainExecutor);
- }
-
- private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture,
- UserHandle owner) {
- try {
- if (mLastScrollCaptureResponse != null) {
- mLastScrollCaptureResponse.close();
- mLastScrollCaptureResponse = null;
- }
- if (responseFuture.isCancelled()) {
- return;
- }
- mLastScrollCaptureResponse = responseFuture.get();
- if (!mLastScrollCaptureResponse.isConnected()) {
- // No connection means that the target window wasn't found
- // or that it cannot support scroll capture.
- Log.d(TAG, "ScrollCapture: " + mLastScrollCaptureResponse.getDescription() + " ["
- + mLastScrollCaptureResponse.getWindowTitle() + "]");
- return;
- }
- Log.d(TAG, "ScrollCapture: connected to window ["
- + mLastScrollCaptureResponse.getWindowTitle() + "]");
-
- final ScrollCaptureResponse response = mLastScrollCaptureResponse;
- mViewProxy.showScrollChip(response.getPackageName(), /* onClick */ () -> {
- Bitmap newScreenshot =
- mImageCapture.captureDisplay(mDisplayId, getFullScreenRect());
-
- if (newScreenshot != null) {
- // delay starting scroll capture to make sure scrim is up before the app
- // moves
- mViewProxy.prepareScrollingTransition(
- response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait,
- () -> runBatchScrollCapture(response, owner));
- } else {
- Log.wtf(TAG, "failed to capture current screenshot for scroll transition");
+ mScrollCaptureExecutor.requestScrollCapture(
+ mDisplayId,
+ mWindow.getDecorView().getWindowToken(),
+ (response) -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
+ 0, response.getPackageName());
+ if (screenshotShelfUi() && mActionsProvider != null) {
+ mActionsProvider.onScrollChipReady(
+ () -> onScrollButtonClicked(owner, response));
+ } else {
+ mViewProxy.showScrollChip(response.getPackageName(),
+ () -> onScrollButtonClicked(owner, response));
+ }
+ return Unit.INSTANCE;
}
- });
- } catch (InterruptedException | ExecutionException e) {
- Log.e(TAG, "requestScrollCapture failed", e);
- }
+ );
}
- ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture;
-
- private void runBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
- // Clear the reference to prevent close() in dismissScreenshot
- mLastScrollCaptureResponse = null;
-
- if (mLongScreenshotFuture != null) {
- mLongScreenshotFuture.cancel(true);
+ private void onScrollButtonClicked(UserHandle owner, ScrollCaptureResponse response) {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "scroll chip tapped");
}
- mLongScreenshotFuture = mScrollCaptureController.run(response);
- mLongScreenshotFuture.addListener(() -> {
- ScrollCaptureController.LongScreenshot longScreenshot;
- try {
- longScreenshot = mLongScreenshotFuture.get();
- } catch (CancellationException e) {
- Log.e(TAG, "Long screenshot cancelled");
- return;
- } catch (InterruptedException | ExecutionException e) {
- Log.e(TAG, "Exception", e);
- mViewProxy.restoreNonScrollingUi();
- return;
- }
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
+ response.getPackageName());
+ Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId, getFullScreenRect());
+ if (newScreenshot == null) {
+ Log.e(TAG, "Failed to capture current screenshot for scroll transition!");
+ return;
+ }
+ // delay starting scroll capture to make sure scrim is up before the app moves
+ mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
+ mScreenshotTakenInPortrait, () -> executeBatchScrollCapture(response, owner));
+ }
- if (longScreenshot.getHeight() == 0) {
- mViewProxy.restoreNonScrollingUi();
- return;
- }
+ private void executeBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
+ mScrollCaptureExecutor.executeBatchScrollCapture(response,
+ () -> {
+ final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent(
+ owner, mContext);
+ mActionIntentExecutor.launchIntentAsync(intent, owner, true,
+ ActivityOptions.makeCustomAnimation(mContext, 0, 0), null);
- mLongScreenshotHolder.setLongScreenshot(longScreenshot);
- mLongScreenshotHolder.setTransitionDestinationCallback(
- (transitionDestination, onTransitionEnd) -> {
- mViewProxy.startLongScreenshotTransition(
- transitionDestination, onTransitionEnd,
- longScreenshot);
- // TODO: Do this via ActionIntentExecutor instead.
- mContext.closeSystemDialogs();
- }
- );
-
- final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
- intent.putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE,
- owner);
- intent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-
- mContext.startActivity(intent,
- ActivityOptions.makeCustomAnimation(mContext, 0, 0).toBundle());
- RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
- SCREENSHOT_REMOTE_RUNNER, 0, 0);
- try {
- WindowManagerGlobal.getWindowManagerService()
- .overridePendingAppTransitionRemote(runner,
- mDisplayId);
- } catch (Exception e) {
- Log.e(TAG, "Error overriding screenshot app transition", e);
- }
- }, mMainExecutor);
+ },
+ mViewProxy::restoreNonScrollingUi,
+ mViewProxy::startLongScreenshotTransition);
}
private void withWindowAttached(Runnable action) {
@@ -919,17 +807,7 @@ public class ScreenshotController {
/** Reset screenshot view and then call onCompleteRunnable */
private void finishDismiss() {
Log.d(TAG, "finishDismiss");
- if (mLastScrollCaptureRequest != null) {
- mLastScrollCaptureRequest.cancel(true);
- mLastScrollCaptureRequest = null;
- }
- if (mLastScrollCaptureResponse != null) {
- mLastScrollCaptureResponse.close();
- mLastScrollCaptureResponse = null;
- }
- if (mLongScreenshotFuture != null) {
- mLongScreenshotFuture.cancel(true);
- }
+ mScrollCaptureExecutor.close();
if (mCurrentRequestCallback != null) {
mCurrentRequestCallback.onFinish();
mCurrentRequestCallback = null;
@@ -942,7 +820,7 @@ public class ScreenshotController {
private void saveScreenshotInBackground(
ScreenshotData screenshot, UUID requestId, Consumer<Uri> finisher) {
ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor,
- requestId, screenshot.getBitmap(), screenshot.getUserHandle(), mDisplayId);
+ requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(), mDisplayId);
future.addListener(() -> {
try {
ImageExporter.Result result = future.get();
@@ -950,12 +828,8 @@ public class ScreenshotController {
logScreenshotResultStatus(result.uri, screenshot.getUserHandle());
mScreenshotHandler.resetTimeout();
if (result.uri != null) {
- final SavedImageData savedImageData = new SavedImageData();
- savedImageData.uri = result.uri;
- savedImageData.owner = screenshot.getUserHandle();
- savedImageData.imageTime = result.timestamp;
- mActionsProvider.setCompletedScreenshot(savedImageData);
- mViewProxy.setChipIntents(savedImageData);
+ mActionsProvider.setCompletedScreenshot(new ScreenshotSavedResult(
+ result.uri, screenshot.getUserOrDefault(), result.timestamp));
}
if (DEBUG_CALLBACK) {
Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) "
@@ -1117,10 +991,6 @@ public class ScreenshotController {
return mDisplayManager.getDisplay(mDisplayId);
}
- private boolean allowLongScreenshots() {
- return !mIsLowRamDevice;
- }
-
private Rect getFullScreenRect() {
DisplayMetrics displayMetrics = new DisplayMetrics();
getDisplay().getRealMetrics(displayMetrics);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
index 92e933a9557b..4fdd90bdcded 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -5,6 +5,7 @@ import android.graphics.Bitmap
import android.graphics.Insets
import android.graphics.Rect
import android.net.Uri
+import android.os.Process
import android.os.UserHandle
import android.view.Display
import android.view.WindowManager.ScreenshotSource
@@ -31,6 +32,10 @@ data class ScreenshotData(
val packageNameString: String
get() = if (topComponent == null) "" else topComponent!!.packageName
+ fun getUserOrDefault(): UserHandle {
+ return userHandle ?: Process.myUserHandle()
+ }
+
companion object {
@JvmStatic
fun fromRequest(request: ScreenshotRequest, displayId: Int = Display.DEFAULT_DISPLAY) =
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
index 3eafbfbf37d7..23f05e0d8337 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
@@ -44,7 +44,7 @@ public class ScreenshotNotificationSmartActionsProvider {
public static final String DEFAULT_ACTION_TYPE = "Smart Action";
/* Define phases of screenshot execution. */
- protected enum ScreenshotOp {
+ public enum ScreenshotOp {
OP_UNKNOWN,
RETRIEVE_SMART_ACTIONS,
REQUEST_SMART_ACTIONS,
@@ -52,7 +52,7 @@ public class ScreenshotNotificationSmartActionsProvider {
}
/* Enum to report success or failure for screenshot execution phases. */
- protected enum ScreenshotOpStatus {
+ public enum ScreenshotOpStatus {
OP_STATUS_UNKNOWN,
SUCCESS,
ERROR,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
index 796457d88cc2..3ad4075a2b89 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
@@ -17,13 +17,14 @@
package com.android.systemui.screenshot
/** Processes a screenshot request sent from [ScreenshotHelper]. */
-interface ScreenshotRequestProcessor {
+fun interface ScreenshotRequestProcessor {
/**
* Inspects the incoming ScreenshotData, potentially modifying it based upon policy.
*
- * @param screenshot the screenshot to process
+ * @param original the screenshot to process
+ * @return a potentially modified screenshot data
*/
- suspend fun process(screenshot: ScreenshotData): ScreenshotData
+ suspend fun process(original: ScreenshotData): ScreenshotData
}
/** Exception thrown by [RequestProcessor] if something goes wrong. */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt
new file mode 100644
index 000000000000..5b6e7ac3e4e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.net.Uri
+import android.os.UserHandle
+import java.text.DateFormat
+import java.util.Date
+
+/**
+ * Represents a saved screenshot, with the uri and user it was saved to as well as the time it was
+ * saved.
+ */
+data class ScreenshotSavedResult(val uri: Uri, val user: UserHandle, val imageTime: Long) {
+ val subject: String
+
+ init {
+ val subjectDate = DateFormat.getDateTimeInstance().format(Date(imageTime))
+ subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate)
+ }
+
+ companion object {
+ private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 65e845749f9e..59e38a836258 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -259,16 +259,8 @@ public class ScreenshotView extends FrameLayout implements
if (DEBUG_SCROLL) {
Log.d(TAG, "Showing Scroll option");
}
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, packageName);
mScrollChip.setVisibility(VISIBLE);
- mScrollChip.setOnClickListener((v) -> {
- if (DEBUG_INPUT) {
- Log.d(TAG, "scroll chip tapped");
- }
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
- packageName);
- onClick.run();
- });
+ mScrollChip.setOnClickListener((v) -> onClick.run());
}
@Override // ViewTreeObserver.OnComputeInternalInsetsListener
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt
deleted file mode 100644
index 2eaff8654a9e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt
+++ /dev/null
@@ -1,285 +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.screenshot
-
-import android.app.Notification
-import android.app.PendingIntent
-import android.content.ClipData
-import android.content.ClipDescription
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.graphics.Bitmap
-import android.net.Uri
-import android.os.Bundle
-import android.os.Process
-import android.os.SystemClock
-import android.os.UserHandle
-import android.provider.DeviceConfig
-import android.util.Log
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
-import com.android.systemui.log.DebugLogger.debugLog
-import com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS
-import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.QUICK_SHARE_ACTION
-import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.REGULAR_SMART_ACTIONS
-import java.util.concurrent.CompletableFuture
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.TimeoutException
-import javax.inject.Inject
-import kotlin.random.Random
-
-/**
- * Handle requesting smart/quickshare actions from the provider and executing an action when the
- * action futures complete.
- */
-class SmartActionsProvider
-@Inject
-constructor(
- private val context: Context,
- private val smartActions: ScreenshotNotificationSmartActionsProvider,
-) {
- /**
- * Requests quick share action for a given screenshot.
- *
- * @param data the ScreenshotData request
- * @param id the request id for the screenshot
- * @param onAction callback to run when quick share action is returned
- */
- fun requestQuickShare(
- data: ScreenshotData,
- id: String,
- onAction: (Notification.Action) -> Unit
- ) {
- val bitmap = data.bitmap ?: return
- val user = data.userHandle ?: return
- val component = data.topComponent ?: ComponentName("", "")
- requestQuickShareAction(id, bitmap, component, user) { quickShareAction ->
- onAction(quickShareAction)
- }
- }
-
- /**
- * Requests smart actions for a given screenshot.
- *
- * @param data the ScreenshotData request
- * @param id the request id for the screenshot
- * @param result the data for the saved image
- * @param onActions callback to run when actions are returned
- */
- fun requestSmartActions(
- data: ScreenshotData,
- id: String,
- result: ScreenshotController.SavedImageData,
- onActions: (List<Notification.Action>) -> Unit
- ) {
- val bitmap = data.bitmap ?: return
- val user = data.userHandle ?: return
- val uri = result.uri ?: return
- val component = data.topComponent ?: ComponentName("", "")
- requestSmartActions(id, bitmap, component, user, uri, REGULAR_SMART_ACTIONS) { actions ->
- onActions(actions)
- }
- }
-
- /**
- * Wraps the given quick share action in a broadcast intent.
- *
- * @param quickShare the quick share action to wrap
- * @param uri the URI of the saved screenshot
- * @param subject the subject/title for the screenshot
- * @param id the request ID of the screenshot
- * @return the wrapped action
- */
- fun wrapIntent(
- quickShare: Notification.Action,
- uri: Uri,
- subject: String,
- id: String
- ): Notification.Action {
- val wrappedIntent: Intent =
- Intent(context, SmartActionsReceiver::class.java)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent)
- .putExtra(
- ScreenshotController.EXTRA_ACTION_INTENT_FILLIN,
- createFillInIntent(uri, subject)
- )
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- val extras: Bundle = quickShare.extras
- val actionType =
- extras.getString(
- ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
- ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE
- )
- // We only query for quick share actions when smart actions are enabled, so we can assert
- // that it's true here.
- wrappedIntent
- .putExtra(ScreenshotController.EXTRA_ACTION_TYPE, actionType)
- .putExtra(ScreenshotController.EXTRA_ID, id)
- .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, true)
- val broadcastIntent =
- PendingIntent.getBroadcast(
- context,
- Random.nextInt(),
- wrappedIntent,
- PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
- )
- return Notification.Action.Builder(quickShare.getIcon(), quickShare.title, broadcastIntent)
- .setContextual(true)
- .addExtras(extras)
- .build()
- }
-
- private fun createFillInIntent(uri: Uri, subject: String): Intent {
- val fillIn = Intent()
- fillIn.setType("image/png")
- fillIn.putExtra(Intent.EXTRA_STREAM, uri)
- fillIn.putExtra(Intent.EXTRA_SUBJECT, subject)
- // Include URI in ClipData also, so that grantPermission picks it up.
- // We don't use setData here because some apps interpret this as "to:".
- val clipData =
- ClipData(ClipDescription("content", arrayOf("image/png")), ClipData.Item(uri))
- fillIn.clipData = clipData
- fillIn.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- return fillIn
- }
-
- private fun requestQuickShareAction(
- id: String,
- image: Bitmap,
- component: ComponentName,
- user: UserHandle,
- timeoutMs: Long = 500,
- onAction: (Notification.Action) -> Unit
- ) {
- requestSmartActions(id, image, component, user, null, QUICK_SHARE_ACTION, timeoutMs) {
- it.firstOrNull()?.let { action -> onAction(action) }
- }
- }
-
- private fun requestSmartActions(
- id: String,
- image: Bitmap,
- component: ComponentName,
- user: UserHandle,
- uri: Uri?,
- actionType: ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType,
- timeoutMs: Long = 500,
- onActions: (List<Notification.Action>) -> Unit
- ) {
- val enabled = isSmartActionsEnabled(user)
- debugLog(DEBUG_ACTIONS) {
- ("getSmartActionsFuture id=$id, uri=$uri, provider=$smartActions, " +
- "actionType=$actionType, smartActionsEnabled=$enabled, userHandle=$user")
- }
- if (!enabled) {
- debugLog(DEBUG_ACTIONS) { "Screenshot Intelligence not enabled, returning empty list" }
- onActions(listOf())
- return
- }
- if (image.config != Bitmap.Config.HARDWARE) {
- debugLog(DEBUG_ACTIONS) {
- "Bitmap expected: Hardware, Bitmap found: ${image.config}. Returning empty list."
- }
- onActions(listOf())
- return
- }
- var smartActionsFuture: CompletableFuture<List<Notification.Action>>
- val startTimeMs = SystemClock.uptimeMillis()
- try {
- smartActionsFuture =
- smartActions.getActions(id, uri, image, component, actionType, user)
- } catch (e: Throwable) {
- val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs
- debugLog(DEBUG_ACTIONS, error = e) {
- "Failed to get future for screenshot notification smart actions."
- }
- notifyScreenshotOp(
- id,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
- waitTimeMs
- )
- onActions(listOf())
- return
- }
- try {
- val actions = smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)
- val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs
- debugLog(DEBUG_ACTIONS) {
- ("Got ${actions.size} smart actions. Wait time: $waitTimeMs ms, " +
- "actionType=$actionType")
- }
- notifyScreenshotOp(
- id,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
- waitTimeMs
- )
- onActions(actions)
- } catch (e: Throwable) {
- val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs
- debugLog(DEBUG_ACTIONS, error = e) {
- "Error getting smart actions. Wait time: $waitTimeMs ms, actionType=$actionType"
- }
- val status =
- if (e is TimeoutException) {
- ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
- } else {
- ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR
- }
- notifyScreenshotOp(
- id,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
- status,
- waitTimeMs
- )
- onActions(listOf())
- }
- }
-
- private fun notifyScreenshotOp(
- screenshotId: String,
- op: ScreenshotNotificationSmartActionsProvider.ScreenshotOp,
- status: ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus,
- durationMs: Long
- ) {
- debugLog(DEBUG_ACTIONS) {
- "$smartActions notifyOp: $op id=$screenshotId, status=$status, durationMs=$durationMs"
- }
- try {
- smartActions.notifyOp(screenshotId, op, status, durationMs)
- } catch (e: Throwable) {
- Log.e(TAG, "Error in notifyScreenshotOp: ", e)
- }
- }
- private fun isSmartActionsEnabled(user: UserHandle): Boolean {
- // Smart actions don't yet work for cross-user saves.
- val savingToOtherUser = user !== Process.myUserHandle()
- val actionsEnabled =
- DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS,
- true
- )
- return !savingToOtherUser && actionsEnabled
- }
-
- companion object {
- private const val TAG = "SmartActionsProvider"
- private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 92d3e550dc0f..ec7707c83980 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -92,14 +92,14 @@ constructor(
// Let's wait before logging "screenshot requested", as we should log the processed
// ScreenshotData.
val screenshotData =
- try {
- screenshotRequestProcessor.process(rawScreenshotData)
- } catch (e: RequestProcessorException) {
- Log.e(TAG, "Failed to process screenshot request!", e)
- logScreenshotRequested(rawScreenshotData)
- onFailedScreenshotRequest(rawScreenshotData, callback)
- return
- }
+ runCatching { screenshotRequestProcessor.process(rawScreenshotData) }
+ .onFailure {
+ Log.e(TAG, "Failed to process screenshot request!", it)
+ logScreenshotRequested(rawScreenshotData)
+ onFailedScreenshotRequest(rawScreenshotData, callback)
+ }
+ .getOrNull()
+ ?: return
logScreenshotRequested(screenshotData)
Log.d(TAG, "Screenshot request: $screenshotData")
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 6ff0fdaeab0f..ab23e5fdb7d9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -22,11 +22,9 @@ import android.app.Service;
import android.view.accessibility.AccessibilityManager;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.screenshot.DefaultScreenshotActionsProvider;
import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
import com.android.systemui.screenshot.LegacyScreenshotViewProxy;
-import com.android.systemui.screenshot.ScreenshotActionsProvider;
import com.android.systemui.screenshot.ScreenshotPolicy;
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
import com.android.systemui.screenshot.ScreenshotShelfViewProxy;
@@ -90,10 +88,6 @@ public abstract class ScreenshotModule {
abstract ScreenshotSoundController bindScreenshotSoundController(
ScreenshotSoundControllerImpl screenshotSoundProviderImpl);
- @Binds
- abstract ScreenshotActionsProvider.Factory bindScreenshotActionsProviderFactory(
- DefaultScreenshotActionsProvider.Factory defaultScreenshotActionsProviderFactory);
-
@Provides
@SysUISingleton
static ScreenshotViewModel providesScreenshotViewModel(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt
new file mode 100644
index 000000000000..c380db0ca3a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.data.model
+
+import android.content.ComponentName
+import android.graphics.Rect
+
+/** A child task within a RootTaskInfo */
+data class ChildTaskModel(
+ /** The task identifier */
+ val id: Int,
+ /** The task name */
+ val name: String,
+ /** The location and size of the task */
+ val bounds: Rect,
+ /** The user which created the task. */
+ val userId: Int,
+) {
+ val componentName: ComponentName?
+ get() = ComponentName.unflattenFromString(name)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
index 837a661230cb..2048b7c0c142 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
@@ -24,6 +24,6 @@ data class DisplayContentModel(
val displayId: Int,
/** Information about the current System UI state which can affect capture. */
val systemUiState: SystemUiState,
- /** A list of root tasks on the display, ordered from bottom to top along the z-axis */
+ /** A list of root tasks on the display, ordered from top to bottom along the z-axis */
val rootTasks: List<RootTaskInfo>,
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
index 9c81b322a2b7..48e813d89af7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
@@ -18,7 +18,7 @@ package com.android.systemui.screenshot.data.repository
import com.android.systemui.screenshot.data.model.DisplayContentModel
/** Provides information about tasks related to a display. */
-interface DisplayContentRepository {
+fun interface DisplayContentRepository {
/** Provides information about the tasks and content presented on a given display. */
suspend fun getDisplayContent(displayId: Int): DisplayContentModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
new file mode 100644
index 000000000000..5e2b57651de7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+
+/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */
+data class CaptureParameters(
+ /** How should the content be captured? */
+ val type: CaptureType,
+ /** The focused or top component at the time of the screenshot. */
+ val component: ComponentName?,
+ /** Which user should receive the screenshot file? */
+ val owner: UserHandle,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
new file mode 100644
index 000000000000..0fb536636f1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+
+/** Contains logic to determine when and how an adjust to screenshot behavior applies. */
+fun interface CapturePolicy {
+ /**
+ * Test the policy against the current display task state. If the policy applies, Returns a
+ * [PolicyResult.Matched] containing [CaptureParameters] used to alter the request.
+ */
+ suspend fun check(content: DisplayContentModel): PolicyResult
+
+ /** The result of a screen capture policy check. */
+ sealed interface PolicyResult {
+ /** The policy rules matched the given display content and will be applied. */
+ data class Matched(
+ /** The name of the policy rule which matched. */
+ val policy: String,
+ /** Why the policy matched. */
+ val reason: String,
+ /** Details on how to modify the screen capture request. */
+ val parameters: CaptureParameters,
+ ) : PolicyResult
+
+ /** The policy rules do not match the given display content and do not apply. */
+ data class NotMatched(
+ /** The name of the policy rule which matched. */
+ val policy: String,
+ /** Why the policy did not match. */
+ val reason: String
+ ) : PolicyResult
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
new file mode 100644
index 000000000000..0ef5207cad37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.graphics.Rect
+
+/** What to capture */
+sealed interface CaptureType {
+ /** Capture the entire screen contents. */
+ data class FullScreen(val displayId: Int) : CaptureType
+
+ /** Capture the contents of the task only. */
+ data class IsolatedTask(
+ val taskId: Int,
+ val taskBounds: Rect?,
+ ) : CaptureType
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
new file mode 100644
index 000000000000..80aa0efb0a19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.app.ActivityTaskManager.RootTaskInfo
+import android.app.WindowConfiguration
+import android.content.ComponentName
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.os.Process.myUserHandle
+import android.os.UserHandle
+import android.util.Log
+import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.screenshot.ImageCapture
+import com.android.systemui.screenshot.ScreenshotData
+import com.android.systemui.screenshot.ScreenshotRequestProcessor
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.repository.DisplayContentRepository
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.Matched
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
+import com.android.systemui.screenshot.policy.CaptureType.FullScreen
+import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+private const val TAG = "PolicyRequestProcessor"
+
+/** A [ScreenshotRequestProcessor] which supports general policy rule matching. */
+class PolicyRequestProcessor(
+ @Background private val background: CoroutineDispatcher,
+ private val capture: ImageCapture,
+ /** Provides information about the tasks on a given display */
+ private val displayTasks: DisplayContentRepository,
+ /** The list of policies to apply, in order of priority */
+ private val policies: List<CapturePolicy>,
+ /** The owner to assign for screenshot when a focused task isn't visible */
+ private val defaultOwner: UserHandle = myUserHandle(),
+ /** The assigned component when no application has focus, or not visible */
+ private val defaultComponent: ComponentName,
+) : ScreenshotRequestProcessor {
+ override suspend fun process(original: ScreenshotData): ScreenshotData {
+ if (original.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ // The request contains an already captured screenshot, accept it as is.
+ Log.i(TAG, "Screenshot bitmap provided. No modifications applied.")
+ return original
+ }
+ val displayContent = displayTasks.getDisplayContent(original.displayId)
+
+ // If policies yield explicit modifications, apply them and return the result
+ Log.i(TAG, "Applying policy checks....")
+ policies.map { policy ->
+ when (val result = policy.check(displayContent)) {
+ is Matched -> {
+ Log.i(TAG, "$result")
+ return modify(original, result.parameters)
+ }
+ is NotMatched -> Log.i(TAG, "$result")
+ }
+ }
+
+ // Otherwise capture normally, filling in additional information as needed.
+ return captureScreenshot(original, displayContent)
+ }
+
+ /** Produce a new [ScreenshotData] using [CaptureParameters] */
+ suspend fun modify(original: ScreenshotData, updates: CaptureParameters): ScreenshotData {
+ // Update and apply bitmap capture depending on the parameters.
+ val updated =
+ when (val type = updates.type) {
+ is IsolatedTask ->
+ replaceWithTaskSnapshot(
+ original,
+ updates.component,
+ updates.owner,
+ type.taskId,
+ type.taskBounds
+ )
+ is FullScreen ->
+ replaceWithScreenshot(
+ original,
+ updates.component,
+ updates.owner,
+ type.displayId
+ )
+ }
+ return updated
+ }
+
+ private suspend fun captureScreenshot(
+ original: ScreenshotData,
+ displayContent: DisplayContentModel,
+ ): ScreenshotData {
+ // The first root task on the display, excluding Picture-in-Picture
+ val topMainRootTask =
+ if (!displayContent.systemUiState.shadeExpanded) {
+ displayContent.rootTasks.firstOrNull(::nonPipVisibleTask)
+ } else {
+ null // Otherwise attributed to SystemUI / current user
+ }
+
+ return replaceWithScreenshot(
+ original = original,
+ componentName = topMainRootTask?.topActivity ?: defaultComponent,
+ owner = topMainRootTask?.userId?.let { UserHandle.of(it) } ?: defaultOwner,
+ displayId = original.displayId
+ )
+ }
+
+ suspend fun replaceWithTaskSnapshot(
+ original: ScreenshotData,
+ componentName: ComponentName?,
+ owner: UserHandle,
+ taskId: Int,
+ taskBounds: Rect?,
+ ): ScreenshotData {
+ Log.i(TAG, "Capturing task snapshot: $componentName / $owner")
+ val taskSnapshot = capture.captureTask(taskId)
+ return original.copy(
+ type = TAKE_SCREENSHOT_PROVIDED_IMAGE,
+ bitmap = taskSnapshot,
+ userHandle = owner,
+ taskId = taskId,
+ topComponent = componentName,
+ screenBounds = taskBounds
+ )
+ }
+
+ suspend fun replaceWithScreenshot(
+ original: ScreenshotData,
+ componentName: ComponentName?,
+ owner: UserHandle?,
+ displayId: Int,
+ ): ScreenshotData {
+ Log.i(TAG, "Capturing screenshot: $componentName / $owner")
+ val screenshot = captureDisplay(displayId)
+ return original.copy(
+ type = TAKE_SCREENSHOT_FULLSCREEN,
+ bitmap = screenshot,
+ userHandle = owner,
+ topComponent = componentName,
+ screenBounds = Rect(0, 0, screenshot?.width ?: 0, screenshot?.height ?: 0)
+ )
+ }
+
+ /** Filter for the task used to attribute a full screen capture to an owner */
+ private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
+ return info.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED &&
+ info.isVisible &&
+ info.isRunning &&
+ info.numActivities > 0 &&
+ info.topActivity != null &&
+ info.childTaskIds.isNotEmpty()
+ }
+
+ /** TODO: Move to ImageCapture (existing function is non-suspending) */
+ private suspend fun captureDisplay(displayId: Int): Bitmap? {
+ return withContext(background) { capture.captureDisplay(displayId) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
new file mode 100644
index 000000000000..d62ab8574799
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.os.UserHandle
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.Matched
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
+import com.android.systemui.screenshot.policy.CaptureType.FullScreen
+import javax.inject.Inject
+
+/**
+ * Condition: When any visible task belongs to a private user.
+ *
+ * Parameters: Capture the whole screen, owned by the private user.
+ */
+class PrivateProfilePolicy
+@Inject
+constructor(
+ private val profileTypes: ProfileTypeRepository,
+) : CapturePolicy {
+ override suspend fun check(content: DisplayContentModel): PolicyResult {
+ // The systemUI notification shade isn't a private profile app, skip.
+ if (content.systemUiState.shadeExpanded) {
+ return NotMatched(policy = NAME, reason = "Notification shade is expanded")
+ }
+
+ // Find the first visible rootTaskInfo with a child task owned by a private user
+ val (rootTask, childTask) =
+ content.rootTasks
+ .filter { it.isVisible }
+ .firstNotNullOfOrNull { root ->
+ root
+ .childTasksTopDown()
+ .firstOrNull {
+ profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
+ }
+ ?.let { root to it }
+ }
+ ?: return NotMatched(policy = NAME, reason = "No private profile tasks are visible")
+
+ // If matched, return parameters needed to modify the request.
+ return Matched(
+ policy = NAME,
+ reason = "At least one private profile task is visible",
+ CaptureParameters(
+ type = FullScreen(content.displayId),
+ component = childTask.componentName ?: rootTask.topActivity,
+ owner = UserHandle.of(childTask.userId),
+ )
+ )
+ }
+ companion object {
+ const val NAME = "PrivateProfile"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
new file mode 100644
index 000000000000..3789371d7c33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.app.ActivityTaskManager.RootTaskInfo
+import com.android.systemui.screenshot.data.model.ChildTaskModel
+
+/** The child tasks of A RootTaskInfo as [ChildTaskModel] in top-down (z-index ascending) order. */
+internal fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> {
+ return ((childTaskIds.size - 1) downTo 0).asSequence().map { index ->
+ ChildTaskModel(
+ childTaskIds[index],
+ childTaskNames[index],
+ childTaskBounds[index],
+ childTaskUserIds[index]
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
index bc71ab71b626..a6b01e748246 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
@@ -16,7 +16,14 @@
package com.android.systemui.screenshot.policy
+import android.content.ComponentName
+import android.content.Context
+import android.os.Process
+import com.android.systemui.Flags.screenshotPrivateProfile
+import com.android.systemui.SystemUIService
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.screenshot.ImageCapture
import com.android.systemui.screenshot.RequestProcessor
import com.android.systemui.screenshot.ScreenshotPolicy
@@ -29,6 +36,7 @@ import dagger.Binds
import dagger.Module
import dagger.Provides
import javax.inject.Provider
+import kotlinx.coroutines.CoroutineDispatcher
@Module
interface ScreenshotPolicyModule {
@@ -37,18 +45,46 @@ interface ScreenshotPolicyModule {
@SysUISingleton
fun bindProfileTypeRepository(impl: ProfileTypeRepositoryImpl): ProfileTypeRepository
+ @Binds
+ @SysUISingleton
+ fun bindDisplayContentRepository(impl: DisplayContentRepositoryImpl): DisplayContentRepository
+
companion object {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun bindCapturePolicyList(
+ privateProfilePolicy: PrivateProfilePolicy,
+ workProfilePolicy: WorkProfilePolicy,
+ ): List<CapturePolicy> {
+ // In order of priority. The first matching policy applies.
+ return listOf(workProfilePolicy, privateProfilePolicy)
+ }
+
+ @JvmStatic
@Provides
@SysUISingleton
fun bindScreenshotRequestProcessor(
+ @Application context: Context,
+ @Background background: CoroutineDispatcher,
imageCapture: ImageCapture,
policyProvider: Provider<ScreenshotPolicy>,
+ displayContentRepoProvider: Provider<DisplayContentRepository>,
+ policyListProvider: Provider<List<CapturePolicy>>,
): ScreenshotRequestProcessor {
- return RequestProcessor(imageCapture, policyProvider.get())
+ return if (screenshotPrivateProfile()) {
+ PolicyRequestProcessor(
+ background = background,
+ capture = imageCapture,
+ displayTasks = displayContentRepoProvider.get(),
+ policies = policyListProvider.get(),
+ defaultOwner = Process.myUserHandle(),
+ defaultComponent =
+ ComponentName(context.packageName, SystemUIService::class.java.toString())
+ )
+ } else {
+ RequestProcessor(imageCapture, policyProvider.get())
+ }
}
}
-
- @Binds
- @SysUISingleton
- fun bindDisplayContentRepository(impl: DisplayContentRepositoryImpl): DisplayContentRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
new file mode 100644
index 000000000000..b781ae99a4de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.os.UserHandle
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
+import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import javax.inject.Inject
+import kotlinx.coroutines.flow.first
+
+/**
+ * Condition: When the top visible task (excluding PIP mode) belongs to a work user.
+ *
+ * Parameters: Capture only the foreground task, owned by the work user.
+ */
+class WorkProfilePolicy
+@Inject
+constructor(
+ private val profileTypes: ProfileTypeRepository,
+) : CapturePolicy {
+
+ override suspend fun check(content: DisplayContentModel): PolicyResult {
+ // The systemUI notification shade isn't a work app, skip.
+ if (content.systemUiState.shadeExpanded) {
+ return NotMatched(policy = NAME, reason = "Notification shade is expanded")
+ }
+
+ // Find the first non PiP rootTask with a top child task owned by a work user
+ val (rootTask, childTask) =
+ content.rootTasks
+ .filter { it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED }
+ .map { it to it.childTasksTopDown().first() }
+ .firstOrNull { (_, child) ->
+ profileTypes.getProfileType(child.userId) == ProfileType.WORK
+ }
+ ?: return NotMatched(
+ policy = NAME,
+ reason = "The top-most non-PINNED task does not belong to a work profile user"
+ )
+
+ // If matched, return parameters needed to modify the request.
+ return PolicyResult.Matched(
+ policy = NAME,
+ reason = "The top-most non-PINNED task ($childTask) belongs to a work profile user",
+ CaptureParameters(
+ type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds),
+ component = childTask.componentName ?: rootTask.topActivity,
+ owner = UserHandle.of(childTask.userId),
+ )
+ )
+ }
+
+ companion object {
+ val NAME = "WorkProfile"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
index 1e1a577ebd4a..706ac9c46be1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
@@ -335,8 +335,8 @@ public class LongScreenshotActivity extends Activity {
// TODO: Fix transition for work profile. Omitting it in the meantime.
mActionExecutor.launchIntentAsync(
ActionIntentCreator.INSTANCE.createEdit(uri, this),
- null,
- mScreenshotUserHandle, false);
+ mScreenshotUserHandle, false,
+ /* activityOptions */ null, /* transitionCoordinator */ null);
} else {
String editorPackage = getString(R.string.config_screenshotEditor);
Intent intent = new Intent(Intent.ACTION_EDIT);
@@ -363,7 +363,8 @@ public class LongScreenshotActivity extends Activity {
private void doShare(Uri uri) {
Intent shareIntent = ActionIntentCreator.INSTANCE.createShare(uri);
- mActionExecutor.launchIntentAsync(shareIntent, null, mScreenshotUserHandle, false);
+ mActionExecutor.launchIntentAsync(shareIntent, mScreenshotUserHandle, false,
+ /* activityOptions */ null, /* transitionCoordinator */ null);
}
private void onClicked(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt
new file mode 100644
index 000000000000..6c4ee3e9e89b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.scroll
+
+import android.app.ActivityManager
+import android.graphics.Rect
+import android.os.IBinder
+import android.util.Log
+import android.view.ScrollCaptureResponse
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.screenshot.scroll.ScrollCaptureController.LongScreenshot
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
+import java.util.concurrent.Future
+import javax.inject.Inject
+
+class ScrollCaptureExecutor
+@Inject
+constructor(
+ activityManager: ActivityManager,
+ private val scrollCaptureClient: ScrollCaptureClient,
+ private val scrollCaptureController: ScrollCaptureController,
+ private val longScreenshotHolder: LongScreenshotData,
+ @Main private val mainExecutor: Executor
+) {
+ private val isLowRamDevice = activityManager.isLowRamDevice
+ private var lastScrollCaptureRequest: ListenableFuture<ScrollCaptureResponse>? = null
+ private var lastScrollCaptureResponse: ScrollCaptureResponse? = null
+ private var longScreenshotFuture: ListenableFuture<LongScreenshot>? = null
+
+ fun requestScrollCapture(
+ displayId: Int,
+ token: IBinder,
+ callback: (ScrollCaptureResponse) -> Unit
+ ) {
+ if (!allowLongScreenshots()) {
+ Log.d(TAG, "Long screenshots not supported on this device")
+ return
+ }
+ scrollCaptureClient.setHostWindowToken(token)
+ lastScrollCaptureRequest?.cancel(true)
+ val scrollRequest =
+ scrollCaptureClient.request(displayId).apply {
+ addListener(
+ { onScrollCaptureResponseReady(this)?.let { callback.invoke(it) } },
+ mainExecutor
+ )
+ }
+ lastScrollCaptureRequest = scrollRequest
+ }
+
+ fun interface ScrollTransitionReady {
+ fun onTransitionReady(
+ destRect: Rect,
+ onTransitionEnd: Runnable,
+ longScreenshot: LongScreenshot
+ )
+ }
+
+ fun executeBatchScrollCapture(
+ response: ScrollCaptureResponse,
+ onCaptureComplete: Runnable,
+ onFailure: Runnable,
+ transition: ScrollTransitionReady,
+ ) {
+ // Clear the reference to prevent close() on reset
+ lastScrollCaptureResponse = null
+ longScreenshotFuture?.cancel(true)
+ longScreenshotFuture =
+ scrollCaptureController.run(response).apply {
+ addListener(
+ {
+ getLongScreenshotChecked(this, onFailure)?.let {
+ longScreenshotHolder.setLongScreenshot(it)
+ longScreenshotHolder.setTransitionDestinationCallback {
+ destinationRect: Rect,
+ onTransitionEnd: Runnable ->
+ transition.onTransitionReady(destinationRect, onTransitionEnd, it)
+ }
+ onCaptureComplete.run()
+ }
+ },
+ mainExecutor
+ )
+ }
+ }
+
+ fun close() {
+ lastScrollCaptureRequest?.cancel(true)
+ lastScrollCaptureRequest = null
+ lastScrollCaptureResponse?.close()
+ lastScrollCaptureResponse = null
+ longScreenshotFuture?.cancel(true)
+ }
+
+ private fun getLongScreenshotChecked(
+ future: ListenableFuture<LongScreenshot>,
+ onFailure: Runnable
+ ): LongScreenshot? {
+ var longScreenshot: LongScreenshot? = null
+ runCatching { longScreenshot = future.get() }
+ .onFailure {
+ Log.e(TAG, "Caught exception", it)
+ onFailure.run()
+ return null
+ }
+ if (longScreenshot?.height != 0) {
+ return longScreenshot
+ }
+ onFailure.run()
+ return null
+ }
+
+ private fun onScrollCaptureResponseReady(
+ responseFuture: Future<ScrollCaptureResponse>
+ ): ScrollCaptureResponse? {
+ try {
+ lastScrollCaptureResponse?.close()
+ lastScrollCaptureResponse = null
+ if (responseFuture.isCancelled) {
+ return null
+ }
+ val captureResponse = responseFuture.get().apply { lastScrollCaptureResponse = this }
+ if (!captureResponse.isConnected) {
+ // No connection means that the target window wasn't found
+ // or that it cannot support scroll capture.
+ Log.d(
+ TAG,
+ "ScrollCapture: ${captureResponse.description} [${captureResponse.windowTitle}]"
+ )
+ return null
+ }
+ Log.d(TAG, "ScrollCapture: connected to window [${captureResponse.windowTitle}]")
+ return captureResponse
+ } catch (e: InterruptedException) {
+ Log.e(TAG, "requestScrollCapture interrupted", e)
+ } catch (e: ExecutionException) {
+ Log.e(TAG, "requestScrollCapture failed", e)
+ }
+ return null
+ }
+
+ private fun allowLongScreenshots(): Boolean {
+ return !isLowRamDevice
+ }
+
+ private companion object {
+ private const val TAG = "ScrollCaptureExecutor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
index a6374ae3304d..3c5a0ec107f8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
@@ -28,16 +28,16 @@ object ActionButtonViewBinder {
fun bind(view: View, viewModel: ActionButtonViewModel) {
val iconView = view.requireViewById<ImageView>(R.id.overlay_action_chip_icon)
val textView = view.requireViewById<TextView>(R.id.overlay_action_chip_text)
- iconView.setImageDrawable(viewModel.icon)
- textView.text = viewModel.name
- setMargins(iconView, textView, viewModel.name?.isNotEmpty() ?: false)
+ iconView.setImageDrawable(viewModel.appearance.icon)
+ textView.text = viewModel.appearance.label
+ setMargins(iconView, textView, viewModel.appearance.label?.isNotEmpty() ?: false)
if (viewModel.onClicked != null) {
view.setOnClickListener { viewModel.onClicked.invoke() }
} else {
view.setOnClickListener(null)
}
view.tag = viewModel.id
- view.contentDescription = viewModel.description
+ view.contentDescription = viewModel.appearance.description
view.visibility = View.VISIBLE
view.alpha = 1f
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index 32e9296107a3..d9a51029d346 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -65,7 +65,9 @@ object ScreenshotShelfViewBinder {
}
launch {
viewModel.actions.collect { actions ->
- if (actions.isNotEmpty()) {
+ val visibleActions = actions.filter { it.visible }
+
+ if (visibleActions.isNotEmpty()) {
view
.requireViewById<View>(R.id.actions_container_background)
.visibility = View.VISIBLE
@@ -75,7 +77,7 @@ object ScreenshotShelfViewBinder {
// any new actions and update any that are already there.
// This assumes that actions can never change order and that each action
// ID is unique.
- val newIds = actions.map { it.id }
+ val newIds = visibleActions.map { it.id }
for (view in actionsContainer.children.toList()) {
if (view.tag !in newIds) {
@@ -83,7 +85,7 @@ object ScreenshotShelfViewBinder {
}
}
- for ((index, action) in actions.withIndex()) {
+ for ((index, action) in visibleActions.withIndex()) {
val currentView: View? = actionsContainer.getChildAt(index)
if (action.id == currentView?.tag) {
// Same ID, update the display
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
index bbab5e0477f7..55a2ad21e292 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
@@ -1,13 +1,11 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, 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
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -15,8 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Name of overlay [CHAR LIMIT=64] -->
- <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
-</resources> \ No newline at end of file
+
+package com.android.systemui.screenshot.ui.viewmodel
+
+import android.graphics.drawable.Drawable
+
+/** Data describing how an action should be shown to the user. */
+data class ActionButtonAppearance(
+ val icon: Drawable?,
+ val label: CharSequence?,
+ val description: CharSequence,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
index 97b24c1b7df7..c5fa8db953fa 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
@@ -16,19 +16,20 @@
package com.android.systemui.screenshot.ui.viewmodel
-import android.graphics.drawable.Drawable
-
data class ActionButtonViewModel(
- val icon: Drawable?,
- val name: CharSequence?,
- val description: CharSequence,
+ val appearance: ActionButtonAppearance,
+ val id: Int,
+ val visible: Boolean,
val onClicked: (() -> Unit)?,
) {
- val id: Int = getId()
-
companion object {
private var nextId = 0
private fun getId() = nextId.also { nextId += 1 }
+
+ fun withNextId(
+ appearance: ActionButtonAppearance,
+ onClicked: (() -> Unit)?
+ ): ActionButtonViewModel = ActionButtonViewModel(appearance, getId(), true, onClicked)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
index ddfa69b687eb..f67ad402a738 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.screenshot.ui.viewmodel
import android.graphics.Bitmap
+import android.util.Log
import android.view.accessibility.AccessibilityManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -39,16 +40,56 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager
_previewAction.value = onClick
}
- fun addAction(action: ActionButtonViewModel) {
+ fun addAction(actionAppearance: ActionButtonAppearance, onClicked: (() -> Unit)): Int {
val actionList = _actions.value.toMutableList()
+ val action = ActionButtonViewModel.withNextId(actionAppearance, onClicked)
actionList.add(action)
_actions.value = actionList
+ return action.id
}
- fun addActions(actions: List<ActionButtonViewModel>) {
+ fun setActionVisibility(actionId: Int, visible: Boolean) {
val actionList = _actions.value.toMutableList()
- actionList.addAll(actions)
- _actions.value = actionList
+ val index = actionList.indexOfFirst { it.id == actionId }
+ if (index >= 0) {
+ actionList[index] =
+ ActionButtonViewModel(
+ actionList[index].appearance,
+ actionId,
+ visible,
+ actionList[index].onClicked
+ )
+ _actions.value = actionList
+ } else {
+ Log.w(TAG, "Attempted to update unknown action id $actionId")
+ }
+ }
+
+ fun updateActionAppearance(actionId: Int, appearance: ActionButtonAppearance) {
+ val actionList = _actions.value.toMutableList()
+ val index = actionList.indexOfFirst { it.id == actionId }
+ if (index >= 0) {
+ actionList[index] =
+ ActionButtonViewModel(
+ appearance,
+ actionId,
+ actionList[index].visible,
+ actionList[index].onClicked
+ )
+ _actions.value = actionList
+ } else {
+ Log.w(TAG, "Attempted to update unknown action id $actionId")
+ }
+ }
+
+ fun removeAction(actionId: Int) {
+ val actionList = _actions.value.toMutableList()
+ if (actionList.removeIf { it.id == actionId }) {
+ // Update if something was removed.
+ _actions.value = actionList
+ } else {
+ Log.w(TAG, "Attempted to remove unknown action id $actionId")
+ }
}
fun reset() {
@@ -56,4 +97,8 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager
_previewAction.value = null
_actions.value = listOf()
}
+
+ companion object {
+ const val TAG = "ScreenshotViewModel"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 92d6ec97ad83..8397d9f1e7b9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -52,7 +52,6 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.util.settings.SecureSettings;
import dagger.assisted.Assisted;
@@ -107,7 +106,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
private ValueAnimator mSliderAnimator;
@Override
- public void setMirror(BrightnessMirrorController controller) {
+ public void setMirror(@Nullable MirrorController controller) {
mControl.setMirrorControllerAndMirror(controller);
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
index 701d814a843b..073279be5af2 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
@@ -16,12 +16,9 @@
package com.android.systemui.settings.brightness
-import com.android.systemui.statusbar.policy.BrightnessMirrorController
-import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener
-
class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController) {
- var mirrorController: BrightnessMirrorController? = null
+ var mirrorController: MirrorController? = null
private set
var brightnessController: MirroredBrightnessController = brightnessController
@@ -30,7 +27,8 @@ class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController
updateBrightnessMirror()
}
- private val brightnessMirrorListener = BrightnessMirrorListener { updateBrightnessMirror() }
+ private val brightnessMirrorListener =
+ MirrorController.BrightnessMirrorListener { updateBrightnessMirror() }
fun onQsPanelAttached() {
mirrorController?.addCallback(brightnessMirrorListener)
@@ -40,7 +38,7 @@ class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController
mirrorController?.removeCallback(brightnessMirrorListener)
}
- fun setController(controller: BrightnessMirrorController?) {
+ fun setController(controller: MirrorController?) {
mirrorController?.removeCallback(brightnessMirrorListener)
mirrorController = controller
mirrorController?.addCallback(brightnessMirrorListener)
@@ -48,6 +46,6 @@ class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController
}
private fun updateBrightnessMirror() {
- mirrorController?.let { brightnessController.setMirror(it) }
+ brightnessController.setMirror(mirrorController)
}
} \ No newline at end of file
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 539b0c2dd599..b425fb997d9e 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -57,8 +57,10 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
ToggleSlider {
private Listener mListener;
+ @Nullable
private ToggleSlider mMirror;
- private BrightnessMirrorController mMirrorController;
+ @Nullable
+ private MirrorController mMirrorController;
private boolean mTracking;
private final FalsingManager mFalsingManager;
private final UiEventLogger mUiEventLogger;
@@ -108,6 +110,9 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
protected void onViewAttached() {
mView.setOnSeekBarChangeListener(mSeekListener);
mView.setOnInterceptListener(mOnInterceptListener);
+ if (mMirror != null) {
+ mView.setOnDispatchTouchEventListener(this::mirrorTouchEvent);
+ }
}
@Override
@@ -129,7 +134,10 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
private boolean copyEventToMirror(MotionEvent ev) {
MotionEvent copy = ev.copy();
- boolean out = mMirror.mirrorTouchEvent(copy);
+ boolean out = false;
+ if (mMirror != null) {
+ out = mMirror.mirrorTouchEvent(copy);
+ }
copy.recycle();
return out;
}
@@ -166,9 +174,13 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
* @param c
*/
@Override
- public void setMirrorControllerAndMirror(BrightnessMirrorController c) {
+ public void setMirrorControllerAndMirror(@Nullable MirrorController c) {
mMirrorController = c;
- setMirror(c.getToggleSlider());
+ if (c != null) {
+ setMirror(c.getToggleSlider());
+ } else {
+ setMirror(null);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt
new file mode 100644
index 000000000000..6a9af26ba4a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness
+
+import android.view.View
+import com.android.systemui.settings.brightness.MirrorController.BrightnessMirrorListener
+import com.android.systemui.statusbar.policy.CallbackController
+
+interface MirrorController : CallbackController<BrightnessMirrorListener> {
+
+ /**
+ * Get the [ToggleSlider] currently associated with this controller, or `null` if none currently
+ */
+ fun getToggleSlider(): ToggleSlider?
+
+ /**
+ * Indicate to this controller that the user is dragging on the brightness view and the mirror
+ * should show
+ */
+ fun showMirror()
+
+ /**
+ * Indicate to this controller that the user has stopped dragging on the brightness view and the
+ * mirror should hide
+ */
+ fun hideMirror()
+
+ /**
+ * Set the location and size of the current brightness [view] in QS so it can be properly
+ * adapted to show the mirror in the same location and with the same size.
+ */
+ fun setLocationAndSize(view: View)
+
+ fun interface BrightnessMirrorListener {
+ fun onBrightnessMirrorReinflated(brightnessMirror: View?)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
index 8d857dec2108..b1a532baa710 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
@@ -22,5 +22,5 @@ import com.android.systemui.statusbar.policy.BrightnessMirrorController
* Indicates controller that has brightness slider and uses [BrightnessMirrorController]
*/
interface MirroredBrightnessController {
- fun setMirror(controller: BrightnessMirrorController)
+ fun setMirror(controller: MirrorController?)
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
index 648e33b1d228..24bc67047a47 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
@@ -19,7 +19,6 @@ package com.android.systemui.settings.brightness;
import android.view.MotionEvent;
import com.android.settingslib.RestrictedLockUtils;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
public interface ToggleSlider {
interface Listener {
@@ -27,7 +26,7 @@ public interface ToggleSlider {
}
void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin);
- void setMirrorControllerAndMirror(BrightnessMirrorController c);
+ void setMirrorControllerAndMirror(MirrorController c);
boolean mirrorTouchEvent(MotionEvent ev);
void setOnChangedListener(Listener l);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt
new file mode 100644
index 000000000000..a0c9be46b6e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class BrightnessMirrorShowingRepository @Inject constructor() {
+ private val _isShowing = MutableStateFlow(false)
+ val isShowing = _isShowing.asStateFlow()
+
+ fun setMirrorShowing(showing: Boolean) {
+ _isShowing.value = showing
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt
new file mode 100644
index 000000000000..ef6e72f76ae6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository
+import javax.inject.Inject
+
+@SysUISingleton
+class BrightnessMirrorShowingInteractor
+@Inject
+constructor(
+ private val brightnessMirrorShowingRepository: BrightnessMirrorShowingRepository,
+) {
+ val isShowing = brightnessMirrorShowingRepository.isShowing
+
+ fun setMirrorShowing(showing: Boolean) {
+ brightnessMirrorShowingRepository.setMirrorShowing(showing)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt
new file mode 100644
index 000000000000..468a87325507
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.ui.binder
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.BrightnessSliderController
+
+object BrightnessMirrorInflater {
+
+ fun inflate(
+ context: Context,
+ sliderControllerFactory: BrightnessSliderController.Factory,
+ ): Pair<View, BrightnessSliderController> {
+ val frame =
+ (LayoutInflater.from(context).inflate(R.layout.brightness_mirror_container, null)
+ as ViewGroup)
+ .apply { isVisible = true }
+ val sliderController = sliderControllerFactory.create(context, frame)
+ sliderController.init()
+ frame.addView(
+ sliderController.rootView,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ return frame to sliderController
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
new file mode 100644
index 000000000000..2651a994bb55
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
@@ -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 com.android.systemui.settings.brightness.ui.viewModel
+
+import android.content.res.Resources
+import android.view.View
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.settings.brightness.MirrorController
+import com.android.systemui.settings.brightness.ToggleSlider
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class BrightnessMirrorViewModel
+@Inject
+constructor(
+ private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor,
+ @Main private val resources: Resources,
+ val sliderControllerFactory: BrightnessSliderController.Factory,
+) : MirrorController {
+
+ private val tempPosition = IntArray(2)
+
+ private var _toggleSlider: BrightnessSliderController? = null
+
+ val isShowing = brightnessMirrorShowingInteractor.isShowing
+
+ private val _locationAndSize: MutableStateFlow<LocationAndSize> =
+ MutableStateFlow(LocationAndSize())
+ val locationAndSize = _locationAndSize.asStateFlow()
+
+ override fun getToggleSlider(): ToggleSlider? {
+ return _toggleSlider
+ }
+
+ fun setToggleSlider(toggleSlider: BrightnessSliderController) {
+ _toggleSlider = toggleSlider
+ }
+
+ override fun showMirror() {
+ brightnessMirrorShowingInteractor.setMirrorShowing(true)
+ }
+
+ override fun hideMirror() {
+ brightnessMirrorShowingInteractor.setMirrorShowing(false)
+ }
+
+ override fun setLocationAndSize(view: View) {
+ view.getLocationInWindow(tempPosition)
+ val padding = resources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
+ _toggleSlider?.rootView?.setPadding(padding, padding, padding, padding)
+ // Account for desired padding
+ _locationAndSize.value =
+ LocationAndSize(
+ yOffset = tempPosition[1] - padding,
+ width = view.measuredWidth + 2 * padding,
+ height = view.measuredHeight + 2 * padding,
+ )
+ }
+
+ // Callbacks are used for indicating reinflation when the config changes in some ways (like
+ // density). However, we don't need that as we recompose the view anyway
+ override fun addCallback(listener: MirrorController.BrightnessMirrorListener) {}
+
+ override fun removeCallback(listener: MirrorController.BrightnessMirrorListener) {}
+}
+
+data class LocationAndSize(
+ val yOffset: Int = 0,
+ val width: Int = 0,
+ val height: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index f6b1bcc0ceea..f418e7e0278f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -23,7 +23,13 @@ import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.compose.ui.platform.ComposeView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.communal.dagger.Communal
@@ -33,6 +39,7 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
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.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -40,6 +47,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.util.kotlin.collectFlow
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
/**
* Controller that's responsible for the glanceable hub container view and its touch handling.
@@ -139,13 +147,33 @@ constructor(
): View {
return initView(
ComposeView(context).apply {
- setContent {
- PlatformTheme {
- CommunalContainer(
- viewModel = communalViewModel,
- dataSourceDelegator = dataSourceDelegator,
- dialogFactory = dialogFactory,
- )
+ repeatWhenAttached {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ setViewTreeOnBackPressedDispatcherOwner(
+ object : OnBackPressedDispatcherOwner {
+ override val onBackPressedDispatcher =
+ OnBackPressedDispatcher().apply {
+ setOnBackInvokedDispatcher(
+ viewRootImpl.onBackInvokedDispatcher
+ )
+ }
+
+ override val lifecycle: Lifecycle =
+ this@repeatWhenAttached.lifecycle
+ }
+ )
+
+ setContent {
+ PlatformTheme {
+ CommunalContainer(
+ viewModel = communalViewModel,
+ dataSourceDelegator = dataSourceDelegator,
+ dialogFactory = dialogFactory,
+ )
+ }
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index f9b4e671f373..903af61f2202 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -81,6 +81,8 @@ public class NotificationShadeWindowView extends WindowRootView {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
+ mInteractionEventHandler.collectKeyEvent(event);
+
if (mInteractionEventHandler.interceptMediaKey(event)) {
return true;
}
@@ -301,6 +303,11 @@ public class NotificationShadeWindowView extends WindowRootView {
boolean dispatchKeyEvent(KeyEvent event);
boolean dispatchKeyEventPreIme(KeyEvent event);
+
+ /**
+ * Collects the KeyEvent without intercepting it
+ */
+ void collectKeyEvent(KeyEvent event);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 324dfdf32a3f..6ac81d226eee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -563,6 +563,11 @@ public class NotificationShadeWindowViewController implements Dumpable {
public boolean dispatchKeyEvent(KeyEvent event) {
return mSysUIKeyEventHandler.dispatchKeyEvent(event);
}
+
+ @Override
+ public void collectKeyEvent(KeyEvent event) {
+ mFalsingCollector.onKeyEvent(event);
+ }
});
mView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index ebebbe65d54b..8c15817898a8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -124,7 +124,6 @@ constructor(
// release focus immediately to kick off focus change transition
notificationShadeWindowController.setNotificationShadeFocusable(false)
notificationStackScrollLayout.cancelExpandHelper()
- sceneInteractor.changeScene(Scenes.Shade, "ShadeController.animateExpandShade")
if (delayed) {
scope.launch {
delay(125)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 9362cd058929..980f665ae61f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -33,6 +33,7 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
@@ -57,6 +58,7 @@ constructor(
val qsSceneAdapter: QSSceneAdapter,
val shadeHeaderViewModel: ShadeHeaderViewModel,
val notifications: NotificationsPlaceholderViewModel,
+ val brightnessMirrorViewModel: BrightnessMirrorViewModel,
val mediaDataManager: MediaDataManager,
shadeInteractor: ShadeInteractor,
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index bb6ee24b0ffe..f8193a4a1b93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -57,6 +57,7 @@ import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.ContrastColorUtil;
+import com.android.systemui.Flags;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.NotificationContentDescription;
import com.android.systemui.statusbar.notification.NotificationDozeHelper;
@@ -208,6 +209,10 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
initializeDecorColor();
reloadDimens();
maybeUpdateIconScaleDimens();
+
+ if (Flags.statusBarMonochromeIconsFix()) {
+ setCropToPadding(true);
+ }
}
/** Should always be preceded by {@link #reloadDimens()} */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 9ce38db1aebe..8b673c951b94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -49,7 +49,6 @@ import android.os.SystemClock;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
import android.view.ContentInfo;
import androidx.annotation.NonNull;
@@ -150,10 +149,8 @@ public final class NotificationEntry extends ListEntry {
private int mCachedContrastColor = COLOR_INVALID;
private int mCachedContrastColorIsFor = COLOR_INVALID;
private InflationTask mRunningTask = null;
- private Throwable mDebugThrowable;
public CharSequence remoteInputTextWhenReset;
public long lastRemoteInputSent = NOT_LAUNCHED_YET;
- public final ArraySet<Integer> mActiveAppOps = new ArraySet<>(3);
private final MutableStateFlow<CharSequence> mHeadsUpStatusBarText =
StateFlowKt.MutableStateFlow(null);
@@ -190,11 +187,6 @@ public final class NotificationEntry extends ListEntry {
private boolean mBlockable;
/**
- * The {@link SystemClock#elapsedRealtime()} when this notification entry was created.
- */
- public long mCreationElapsedRealTime;
-
- /**
* Whether this notification has ever been a non-sticky HUN.
*/
private boolean mIsDemoted = false;
@@ -264,13 +256,8 @@ public final class NotificationEntry extends ListEntry {
mKey = sbn.getKey();
setSbn(sbn);
setRanking(ranking);
- mCreationElapsedRealTime = SystemClock.elapsedRealtime();
}
- @VisibleForTesting
- public void setCreationElapsedRealTime(long time) {
- mCreationElapsedRealTime = time;
- }
@Override
public NotificationEntry getRepresentativeEntry() {
return this;
@@ -581,19 +568,6 @@ public final class NotificationEntry extends ListEntry {
return mRunningTask;
}
- /**
- * Set a throwable that is used for debugging
- *
- * @param debugThrowable the throwable to save
- */
- public void setDebugThrowable(Throwable debugThrowable) {
- mDebugThrowable = debugThrowable;
- }
-
- public Throwable getDebugThrowable() {
- return mDebugThrowable;
- }
-
public void onRemoteInputInserted() {
lastRemoteInputSent = NOT_LAUNCHED_YET;
remoteInputTextWhenReset = null;
@@ -749,12 +723,6 @@ public final class NotificationEntry extends ListEntry {
return row != null && row.areChildrenExpanded();
}
-
- //TODO: probably less confusing to say "is group fully visible"
- public boolean isGroupNotFullyVisible() {
- return row == null || row.isGroupNotFullyVisible();
- }
-
public NotificationGuts getGuts() {
if (row != null) return row.getGuts();
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index f689e7e60fc1..968b591b9a09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -45,6 +45,7 @@ import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.row.FooterViewButton;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.util.DrawableDumpKt;
@@ -457,6 +458,12 @@ public class FooterView extends StackScrollerDecorView {
*/
public boolean hideContent;
+ /**
+ * When true, skip animating Y on the next #animateTo.
+ * Once true, remains true until reset in #animateTo.
+ */
+ public boolean resetY = false;
+
@Override
public void copyFrom(ViewState viewState) {
super.copyFrom(viewState);
@@ -473,5 +480,17 @@ public class FooterView extends StackScrollerDecorView {
footerView.setContentVisibleAnimated(!hideContent);
}
}
+
+ @Override
+ public void animateTo(View child, AnimationProperties properties) {
+ if (child instanceof FooterView) {
+ // Must set animateY=false before super.animateTo, which checks for animateY
+ if (resetY) {
+ properties.getAnimationFilter().animateY = false;
+ resetY = false;
+ }
+ }
+ super.animateTo(child, properties);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index c4d9ab7a47c2..9619acaed441 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -27,6 +27,7 @@ import android.database.ContentObserver
import android.hardware.display.AmbientDisplayConfiguration
import android.os.Handler
import android.os.PowerManager
+import android.provider.Settings
import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
import android.provider.Settings.Global.HEADS_UP_OFF
import com.android.systemui.dagger.qualifiers.Main
@@ -42,6 +43,7 @@ import com.android.systemui.statusbar.notification.interruption.VisualInterrupti
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SystemSettings
import com.android.systemui.util.time.SystemClock
class PeekDisabledSuppressor(
@@ -231,6 +233,7 @@ class AlertKeyguardVisibilitySuppressor(
class AvalancheSuppressor(
private val avalancheProvider: AvalancheProvider,
private val systemClock: SystemClock,
+ private val systemSettings: SystemSettings,
) :
VisualInterruptionFilter(
types = setOf(PEEK, PULSE),
@@ -253,12 +256,23 @@ class AvalancheSuppressor(
}
override fun shouldSuppress(entry: NotificationEntry): Boolean {
- val timeSinceAvalanche = systemClock.currentTimeMillis() - avalancheProvider.startTime
- val isActive = timeSinceAvalanche < avalancheProvider.timeoutMs
+ if (!isCooldownEnabled()) {
+ reason = "FALSE avalanche cooldown setting DISABLED"
+ return false
+ }
+ val timeSinceAvalancheMs = systemClock.currentTimeMillis() - avalancheProvider.startTime
+ val timedOut = timeSinceAvalancheMs >= avalancheProvider.timeoutMs
+ if (timedOut) {
+ reason = "FALSE avalanche event TIMED OUT. " +
+ "${timeSinceAvalancheMs/1000} seconds since last avalanche"
+ return false
+ }
val state = calculateState(entry)
- val suppress = isActive && state == State.SUPPRESS
- reason = "avalanche suppress=$suppress isActive=$isActive state=$state"
- return suppress
+ if (state != State.SUPPRESS) {
+ reason = "FALSE avalanche IN ALLOWLIST: $state"
+ return false
+ }
+ return true
}
private fun calculateState(entry: NotificationEntry): State {
@@ -294,4 +308,11 @@ class AvalancheSuppressor(
}
return State.SUPPRESS
}
+
+ private fun isCooldownEnabled(): Boolean {
+ return systemSettings.getInt(
+ Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+ /* def */ 1
+ ) == 1
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 375b6e5cb6a3..e6d97c211dc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -40,6 +40,7 @@ import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.EventLog
import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SystemSettings
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -61,7 +62,8 @@ constructor(
private val systemClock: SystemClock,
private val uiEventLogger: UiEventLogger,
private val userTracker: UserTracker,
- private val avalancheProvider: AvalancheProvider
+ private val avalancheProvider: AvalancheProvider,
+ private val systemSettings: SystemSettings
) : VisualInterruptionDecisionProvider {
init {
@@ -170,7 +172,7 @@ constructor(
addFilter(AlertKeyguardVisibilitySuppressor(keyguardNotificationVisibilityProvider))
if (NotificationAvalancheSuppression.isEnabled) {
- addFilter(AvalancheSuppressor(avalancheProvider, systemClock))
+ addFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings))
avalancheProvider.register()
}
started = true
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 eb6c7b520037..9e0b16cfb312 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
@@ -1767,7 +1767,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
this(context, attrs, context);
- Log.wtf(TAG, "This constructor shouldn't be called");
+ Log.e(TAG, "This constructor shouldn't be called");
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
index d6118a0b3865..d3c874ce04d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row
+import android.app.Flags
import android.app.Notification
import android.app.Notification.MessagingStyle
import android.app.Person
@@ -131,7 +132,7 @@ internal object SingleLineViewInflater {
val senderName =
systemUiContext.resources.getString(
R.string.conversation_single_line_name_display,
- name
+ if (Flags.cleanUpSpansAndNewLines()) name?.toString() else name
)
// We need to find back-up values for those texts if they are needed and empty
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
index 03a108287087..0c248f5949cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
@@ -30,7 +30,7 @@ public class AnimationFilter {
public static final int NO_DELAY = -1;
boolean animateAlpha;
boolean animateX;
- boolean animateY;
+ public boolean animateY;
ArraySet<View> animateYViews = new ArraySet<>();
boolean animateZ;
boolean animateHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 5dc37e0525da..92c597cf384e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -56,6 +56,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.HybridGroupManager;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
@@ -1429,7 +1430,7 @@ public class NotificationChildrenContainer extends ViewGroup
if (singleLineView != null) {
minExpandHeight += singleLineView.getHeight();
} else {
- if (AsyncGroupHeaderViewInflation.isEnabled()) {
+ if (AsyncHybridViewInflation.isEnabled()) {
minExpandHeight += mMinSingleLineHeight;
} else {
Log.e(TAG, "getMinHeight: child " + child.getEntry().getKey()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index fedb88d29837..82559dec9f86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1868,6 +1868,13 @@ public class NotificationStackScrollLayout
private void updateImeInset(WindowInsets windowInsets) {
mImeInset = windowInsets.getInsets(WindowInsets.Type.ime()).bottom;
+ if (mFooterView != null && mFooterView.getViewState() != null) {
+ // Do not animate footer Y when showing IME so that after IME hides, the footer
+ // appears at the correct Y. Once resetY is true, it remains true (even when IME
+ // hides, where mImeInset=0) until reset in FooterViewState#animateTo.
+ ((FooterView.FooterViewState) mFooterView.getViewState()).resetY |= mImeInset > 0;
+ }
+
if (mForcedScroll != null) {
updateForcedScroll();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
index 7e3e724bfed9..832e69080f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
@@ -30,11 +30,15 @@ data class ShadeScrimBounds(
/** The current height of the notification container. */
val height: Float = bottom - top
- operator fun minus(position: ViewPosition) =
- ShadeScrimBounds(
- left = left - position.left,
- top = top - position.top,
- right = right - position.left,
- bottom = bottom - position.top,
- )
+ fun minus(leftOffset: Int = 0, topOffset: Int = 0) =
+ if (leftOffset == 0 && topOffset == 0) {
+ this
+ } else {
+ ShadeScrimBounds(
+ left = this.left - leftOffset,
+ top = this.top - topOffset,
+ right = this.right - leftOffset,
+ bottom = this.bottom - topOffset,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index a98717a743d8..047b560afbc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
-import android.view.View
+import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.common.ui.ConfigurationState
@@ -26,7 +26,6 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.stack.shared.model.ViewPosition
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationScrollViewModel
import com.android.systemui.util.kotlin.FlowDumperImpl
@@ -37,9 +36,6 @@ import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
/** Binds the [NotificationScrollView]. */
@@ -54,8 +50,15 @@ constructor(
private val configuration: ConfigurationState,
) : FlowDumperImpl(dumpManager) {
- private val viewPosition = MutableStateFlow(ViewPosition()).dumpValue("viewPosition")
- private val viewTopOffset = viewPosition.map { it.top }.distinctUntilChanged()
+ private val viewLeftOffset = MutableStateFlow(0).dumpValue("viewLeftOffset")
+
+ private fun updateViewPosition() {
+ val trueView = view.asView()
+ if (trueView.top != 0) {
+ Log.w("NSSL", "Expected $trueView to have top==0")
+ }
+ viewLeftOffset.value = trueView.left
+ }
fun bindWhileAttached(): DisposableHandle {
return view.asView().repeatWhenAttached(mainImmediateDispatcher) {
@@ -65,20 +68,20 @@ constructor(
suspend fun bind() = coroutineScope {
launchAndDispose {
- viewPosition.value = view.asView().position
- view.asView().onLayoutChanged { viewPosition.value = it.position }
+ updateViewPosition()
+ view.asView().onLayoutChanged { updateViewPosition() }
}
launch {
- viewModel.shadeScrimShape(scrimRadius, viewPosition).collect {
- view.setScrimClippingShape(it)
- }
+ viewModel
+ .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset)
+ .collect { view.setScrimClippingShape(it) }
}
- launch { viewModel.stackTop.minusTopOffset().collect { view.setStackTop(it) } }
- launch { viewModel.stackBottom.minusTopOffset().collect { view.setStackBottom(it) } }
+ launch { viewModel.stackTop.collect { view.setStackTop(it) } }
+ launch { viewModel.stackBottom.collect { view.setStackBottom(it) } }
launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } }
- launch { viewModel.headsUpTop.minusTopOffset().collect { view.setHeadsUpTop(it) } }
+ launch { viewModel.headsUpTop.collect { view.setHeadsUpTop(it) } }
launch { viewModel.expandFraction.collect { view.setExpandFraction(it) } }
launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
@@ -94,15 +97,7 @@ constructor(
}
}
- /** Combine with the topOffset flow and subtract that value from this flow's value */
- private fun Flow<Float>.minusTopOffset() =
- combine(viewTopOffset) { y, topOffset -> y - topOffset }
-
/** flow of the scrim clipping radius */
private val scrimRadius: Flow<Int>
get() = configuration.getDimensionPixelOffset(R.dimen.notification_scrim_corner_radius)
-
- /** Construct a [ViewPosition] from this view using [View.getLeft] and [View.getTop] */
- private val View.position
- get() = ViewPosition(left = left, top = top)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index cfd19bacdc69..741102bcd574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -21,11 +21,14 @@ import android.view.WindowInsets
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags.communalHub
+import com.android.systemui.common.ui.view.onApplyWindowInsets
+import com.android.systemui.common.ui.view.onLayoutChanged
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -137,10 +140,12 @@ constructor(
}
}
- launch {
- burnInParams
- .flatMapLatest { params -> viewModel.translationY(params) }
- .collect { y -> controller.setTranslationY(y) }
+ if (!SceneContainerFlag.isEnabled) {
+ launch {
+ burnInParams
+ .flatMapLatest { params -> viewModel.translationY(params) }
+ .collect { y -> controller.setTranslationY(y) }
+ }
}
launch { viewModel.translationX.collect { x -> controller.translationX = x } }
@@ -168,22 +173,16 @@ constructor(
controller.setOnHeightChangedRunnable { viewModel.notificationStackChanged() }
disposables += DisposableHandle { controller.setOnHeightChangedRunnable(null) }
- view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
- val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
- burnInParams.update { current ->
- current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+ disposables +=
+ view.onApplyWindowInsets { _: View, insets: WindowInsets ->
+ val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
+ burnInParams.update { current ->
+ current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+ }
+ insets
}
- insets
- }
- disposables += DisposableHandle { view.setOnApplyWindowInsetsListener(null) }
- // Required to capture keyguard media changes and ensure the notification count is correct
- val layoutChangeListener =
- View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
- viewModel.notificationStackChanged()
- }
- view.addOnLayoutChangeListener(layoutChangeListener)
- disposables += DisposableHandle { view.removeOnLayoutChangeListener(layoutChangeListener) }
+ disposables += view.onLayoutChanged { viewModel.notificationStackChanged() }
return disposables
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 9483f3310316..516ec319ceb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -27,7 +27,6 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
-import com.android.systemui.statusbar.notification.stack.shared.model.ViewPosition
import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -92,12 +91,12 @@ constructor(
fun shadeScrimShape(
cornerRadius: Flow<Int>,
- viewPosition: Flow<ViewPosition>
+ viewLeftOffset: Flow<Int>
): Flow<ShadeScrimShape?> =
- combine(shadeScrimClipping, cornerRadius, viewPosition) { clipping, radius, position ->
+ combine(shadeScrimClipping, cornerRadius, viewLeftOffset) { clipping, radius, leftOffset ->
if (clipping == null) return@combine null
ShadeScrimShape(
- bounds = clipping.bounds - position,
+ bounds = clipping.bounds.minus(leftOffset = leftOffset),
topRadius = radius.takeIf { clipping.rounding.isTopRounded } ?: 0,
bottomRadius = radius.takeIf { clipping.rounding.isBottomRounded } ?: 0
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 692368de5aea..13e36d58f6a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -19,6 +19,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import androidx.annotation.VisibleForTesting
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -166,23 +167,35 @@ constructor(
)
.dumpWhileCollecting("isShadeLocked")
+ @VisibleForTesting
+ val paddingTopDimen: Flow<Int> =
+ interactor.configurationBasedDimensions
+ .map {
+ when {
+ !it.useSplitShade -> 0
+ it.useLargeScreenHeader -> it.marginTopLargeScreen
+ else -> it.marginTop
+ }
+ }
+ .distinctUntilChanged()
+ .dumpWhileCollecting("paddingTopDimen")
+
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
interactor.configurationBasedDimensions
.map {
val marginTop =
- if (it.useLargeScreenHeader) it.marginTopLargeScreen else it.marginTop
+ when {
+ // y position of the NSSL in the window needs to be 0 under scene container
+ SceneContainerFlag.isEnabled -> 0
+ it.useLargeScreenHeader -> it.marginTopLargeScreen
+ else -> it.marginTop
+ }
ConfigurationBasedDimensions(
marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
marginEnd = it.marginHorizontal,
marginBottom = it.marginBottom,
marginTop = marginTop,
useSplitShade = it.useSplitShade,
- paddingTop =
- if (it.useSplitShade) {
- marginTop
- } else {
- 0
- },
)
}
.distinctUntilChanged()
@@ -337,20 +350,21 @@ constructor(
*
* When the shade is expanding, the position is controlled by... the shade.
*/
- val bounds: StateFlow<NotificationContainerBounds> =
+ val bounds: StateFlow<NotificationContainerBounds> by lazy {
+ SceneContainerFlag.assertInLegacyMode()
combine(
isOnLockscreenWithoutShade,
keyguardInteractor.notificationContainerBounds,
- configurationBasedDimensions,
+ paddingTopDimen,
interactor.topPosition
.sampleCombine(
keyguardTransitionInteractor.isInTransitionToAnyState,
shadeInteractor.qsExpansion,
)
.onStart { emit(Triple(0f, false, 0f)) }
- ) { onLockscreen, bounds, config, (top, isInTransitionToAnyState, qsExpansion) ->
+ ) { onLockscreen, bounds, paddingTop, (top, isInTransitionToAnyState, qsExpansion) ->
if (onLockscreen) {
- bounds.copy(top = bounds.top - config.paddingTop)
+ bounds.copy(top = bounds.top - paddingTop)
} else {
// When QS expansion > 0, it should directly set the top padding so do not
// animate it
@@ -367,10 +381,7 @@ constructor(
initialValue = NotificationContainerBounds(),
)
.dumpValue("bounds")
- get() {
- /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode()
- return field
- }
+ }
/**
* Ensure view is visible when the shade/qs are expanded. Also, as QS is expanding, fade out
@@ -546,6 +557,8 @@ constructor(
* translated as the keyguard fades out.
*/
fun translationY(params: BurnInParameters): Flow<Float> {
+ // with SceneContainer, x translation is handled by views, y is handled by compose
+ SceneContainerFlag.assertInLegacyMode()
return combine(
aodBurnInViewModel
.movement(params)
@@ -644,6 +657,5 @@ constructor(
val marginEnd: Int,
val marginBottom: Int,
val useSplitShade: Boolean,
- val paddingTop: Int,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 37646aea86e2..9268d1658b80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -14,51 +14,20 @@
package com.android.systemui.statusbar.phone
-import android.app.ActivityManager
-import android.app.ActivityOptions
-import android.app.ActivityTaskManager
import android.app.PendingIntent
-import android.app.TaskStackBuilder
-import android.content.Context
import android.content.Intent
import android.os.Bundle
-import android.os.RemoteException
import android.os.UserHandle
-import android.provider.Settings
-import android.util.Log
-import android.view.RemoteAnimationAdapter
import android.view.View
-import android.view.WindowManager
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.ActivityIntentHelper
import com.android.systemui.animation.ActivityTransitionAnimator
-import com.android.systemui.animation.ActivityTransitionAnimator.PendingIntentStarter
-import com.android.systemui.animation.DelegateTransitionAnimatorController
-import com.android.systemui.assist.AssistManager
-import com.android.systemui.camera.CameraIntents.Companion.isInsecureCameraIntent
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.KeyguardViewMediator
-import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
-import com.android.systemui.res.R
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.kotlin.getOrNull
import dagger.Lazy
-import java.util.Optional
import javax.inject.Inject
/** Handles start activity logic in SystemUI. */
@@ -66,38 +35,18 @@ import javax.inject.Inject
class ActivityStarterImpl
@Inject
constructor(
- private val centralSurfacesOptLazy: Lazy<Optional<CentralSurfaces>>,
- private val assistManagerLazy: Lazy<AssistManager>,
- private val dozeServiceHostLazy: Lazy<DozeServiceHost>,
- private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
- private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
- private val shadeControllerLazy: Lazy<ShadeController>,
- private val commandQueue: CommandQueue,
- private val shadeAnimationInteractor: ShadeAnimationInteractor,
- private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>,
- private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
- private val activityTransitionAnimator: ActivityTransitionAnimator,
- private val context: Context,
- @DisplayId private val displayId: Int,
- private val lockScreenUserManager: NotificationLockscreenUserManager,
- private val statusBarWindowController: StatusBarWindowController,
- private val wakefulnessLifecycle: WakefulnessLifecycle,
- private val keyguardStateController: KeyguardStateController,
private val statusBarStateController: SysuiStatusBarStateController,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val deviceProvisionedController: DeviceProvisionedController,
- private val userTracker: UserTracker,
- private val activityIntentHelper: ActivityIntentHelper,
@Main private val mainExecutor: DelayableExecutor,
+ legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl>,
+ activityStarterInternal: Lazy<ActivityStarterInternalImpl>,
) : ActivityStarter {
- companion object {
- const val TAG = "ActivityStarterImpl"
- }
-
- private val centralSurfaces: CentralSurfaces?
- get() = centralSurfacesOptLazy.get().getOrNull()
- private val activityStarterInternal = ActivityStarterInternal()
+ private val activityStarterInternal: ActivityStarterInternal =
+ if (SceneContainerFlag.isEnabled) {
+ activityStarterInternal.get()
+ } else {
+ legacyActivityStarter.get()
+ }
override fun startPendingIntentDismissingKeyguard(intent: PendingIntent) {
activityStarterInternal.startPendingIntentDismissingKeyguard(intent = intent)
@@ -401,575 +350,11 @@ constructor(
}
}
- private fun postOnUiThread(delay: Int = 0, runnable: Runnable) {
- mainExecutor.executeDelayed(runnable, delay.toLong())
- }
-
- /**
- * Whether we should animate an activity launch.
- *
- * Note: This method must be called *before* dismissing the keyguard.
- */
- private fun shouldAnimateLaunch(
- isActivityIntent: Boolean,
- showOverLockscreen: Boolean,
- ): Boolean {
- // TODO(b/294418322): Support launch animations when occluded.
- if (keyguardStateController.isOccluded) {
- return false
- }
-
- // Always animate if we are not showing the keyguard or if we animate over the lockscreen
- // (without unlocking it).
- if (showOverLockscreen || !keyguardStateController.isShowing) {
- return true
- }
-
- // We don't animate non-activity launches as they can break the animation.
- // TODO(b/184121838): Support non activity launches on the lockscreen.
- return isActivityIntent
- }
-
override fun shouldAnimateLaunch(isActivityIntent: Boolean): Boolean {
- return shouldAnimateLaunch(isActivityIntent, false)
+ return activityStarterInternal.shouldAnimateLaunch(isActivityIntent)
}
- /**
- * Encapsulates the activity logic for activity starter.
- *
- * Logic is duplicated in {@link CentralSurfacesImpl}
- */
- private inner class ActivityStarterInternal {
- /** Starts an activity after dismissing keyguard. */
- fun startActivityDismissingKeyguard(
- intent: Intent,
- onlyProvisioned: Boolean = false,
- dismissShade: Boolean = false,
- disallowEnterPictureInPictureWhileLaunching: Boolean = false,
- callback: ActivityStarter.Callback? = null,
- flags: Int = 0,
- animationController: ActivityTransitionAnimator.Controller? = null,
- userHandle: UserHandle? = null,
- customMessage: String? = null,
- ) {
- val userHandle: UserHandle = userHandle ?: getActivityUserHandle(intent)
-
- if (onlyProvisioned && !deviceProvisionedController.isDeviceProvisioned) return
-
- val willLaunchResolverActivity: Boolean =
- activityIntentHelper.wouldLaunchResolverActivity(
- intent,
- lockScreenUserManager.currentUserId
- )
-
- val animate =
- animationController != null &&
- !willLaunchResolverActivity &&
- shouldAnimateLaunch(isActivityIntent = true)
- val animController =
- wrapAnimationControllerForShadeOrStatusBar(
- animationController = animationController,
- dismissShade = dismissShade,
- isLaunchForActivity = true,
- )
-
- // If we animate, we will dismiss the shade only once the animation is done. This is
- // taken care of by the StatusBarLaunchAnimationController.
- val dismissShadeDirectly = dismissShade && animController == null
-
- val runnable = Runnable {
- assistManagerLazy.get().hideAssist()
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
- intent.addFlags(flags)
- val result = intArrayOf(ActivityManager.START_CANCELED)
- activityTransitionAnimator.startIntentWithAnimation(
- animController,
- animate,
- intent.getPackage()
- ) { adapter: RemoteAnimationAdapter? ->
- val options =
- ActivityOptions(CentralSurfaces.getActivityOptions(displayId, adapter))
-
- // We know that the intent of the caller is to dismiss the keyguard and
- // this runnable is called right after the keyguard is solved, so we tell
- // WM that we should dismiss it to avoid flickers when opening an activity
- // that can also be shown over the keyguard.
- options.setDismissKeyguardIfInsecure()
- options.setDisallowEnterPictureInPictureWhileLaunching(
- disallowEnterPictureInPictureWhileLaunching
- )
- if (isInsecureCameraIntent(intent)) {
- // Normally an activity will set it's requested rotation
- // animation on its window. However when launching an activity
- // causes the orientation to change this is too late. In these cases
- // the default animation is used. This doesn't look good for
- // the camera (as it rotates the camera contents out of sync
- // with physical reality). So, we ask the WindowManager to
- // force the cross fade animation if an orientation change
- // happens to occur during the launch.
- options.rotationAnimationHint =
- WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
- }
- if (Settings.Panel.ACTION_VOLUME == intent.action) {
- // Settings Panel is implemented as activity(not a dialog), so
- // underlying app is paused and may enter picture-in-picture mode
- // as a result.
- // So we need to disable picture-in-picture mode here
- // if it is volume panel.
- options.setDisallowEnterPictureInPictureWhileLaunching(true)
- }
- try {
- result[0] =
- ActivityTaskManager.getService()
- .startActivityAsUser(
- null,
- context.basePackageName,
- context.attributionTag,
- intent,
- intent.resolveTypeIfNeeded(context.contentResolver),
- null,
- null,
- 0,
- Intent.FLAG_ACTIVITY_NEW_TASK,
- null,
- options.toBundle(),
- userHandle.identifier,
- )
- } catch (e: RemoteException) {
- Log.w(TAG, "Unable to start activity", e)
- }
- result[0]
- }
- callback?.onActivityStarted(result[0])
- }
- val cancelRunnable = Runnable {
- callback?.onActivityStarted(ActivityManager.START_CANCELED)
- }
- // Do not deferKeyguard when occluded because, when keyguard is occluded,
- // we do not launch the activity until keyguard is done.
- val occluded = (keyguardStateController.isShowing && keyguardStateController.isOccluded)
- val deferred = !occluded
- executeRunnableDismissingKeyguard(
- runnable,
- cancelRunnable,
- dismissShadeDirectly,
- willLaunchResolverActivity,
- deferred,
- animate,
- customMessage,
- )
- }
-
- /**
- * Starts a pending intent after dismissing keyguard.
- *
- * This can be called in a background thread (to prevent calls in [ActivityIntentHelper] in
- * the main thread).
- */
- fun startPendingIntentDismissingKeyguard(
- intent: PendingIntent,
- intentSentUiThreadCallback: Runnable? = null,
- associatedView: View? = null,
- animationController: ActivityTransitionAnimator.Controller? = null,
- showOverLockscreen: Boolean = false,
- fillInIntent: Intent? = null,
- extraOptions: Bundle? = null,
- ) {
- val animationController =
- if (associatedView is ExpandableNotificationRow) {
- centralSurfaces?.getAnimatorControllerFromNotification(associatedView)
- } else animationController
-
- val willLaunchResolverActivity =
- (intent.isActivity &&
- activityIntentHelper.wouldPendingLaunchResolverActivity(
- intent,
- lockScreenUserManager.currentUserId,
- ))
-
- val actuallyShowOverLockscreen =
- showOverLockscreen &&
- intent.isActivity &&
- activityIntentHelper.wouldPendingShowOverLockscreen(
- intent,
- lockScreenUserManager.currentUserId
- )
-
- val animate =
- !willLaunchResolverActivity &&
- animationController != null &&
- shouldAnimateLaunch(intent.isActivity, actuallyShowOverLockscreen)
-
- // We wrap animationCallback with a StatusBarLaunchAnimatorController so
- // that the shade is collapsed after the animation (or when it is cancelled,
- // aborted, etc).
- val statusBarController =
- wrapAnimationControllerForShadeOrStatusBar(
- animationController = animationController,
- dismissShade = true,
- isLaunchForActivity = intent.isActivity,
- )
- val controller =
- if (actuallyShowOverLockscreen) {
- wrapAnimationControllerForLockscreen(statusBarController)
- } else {
- statusBarController
- }
-
- // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
- // run the animation on the keyguard). The animation will take care of (instantly)
- // collapsing the shade and hiding the keyguard once it is done.
- val collapse = !animate
- val runnable = Runnable {
- try {
- activityTransitionAnimator.startPendingIntentWithAnimation(
- controller,
- animate,
- intent.creatorPackage,
- actuallyShowOverLockscreen,
- object : PendingIntentStarter {
- override fun startPendingIntent(
- animationAdapter: RemoteAnimationAdapter?
- ): Int {
- val options =
- ActivityOptions(
- CentralSurfaces.getActivityOptions(
- displayId,
- animationAdapter
- )
- .apply { extraOptions?.let { putAll(it) } }
- )
- // TODO b/221255671: restrict this to only be set for
- // notifications
- options.isEligibleForLegacyPermissionPrompt = true
- options.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- )
- return intent.sendAndReturnResult(
- context,
- 0,
- fillInIntent,
- null,
- null,
- null,
- options.toBundle()
- )
- }
- },
- )
- } catch (e: PendingIntent.CanceledException) {
- // the stack trace isn't very helpful here.
- // Just log the exception message.
- Log.w(TAG, "Sending intent failed: $e")
- if (!collapse) {
- // executeRunnableDismissingKeyguard did not collapse for us already.
- shadeControllerLazy.get().collapseOnMainThread()
- }
- // TODO: Dismiss Keyguard.
- }
- if (intent.isActivity) {
- assistManagerLazy.get().hideAssist()
- // This activity could have started while the device is dreaming, in which case
- // the dream would occlude the activity. In order to show the newly started
- // activity, we wake from the dream.
- keyguardUpdateMonitor.awakenFromDream()
- }
- intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
- }
-
- if (!actuallyShowOverLockscreen) {
- postOnUiThread(delay = 0) {
- executeRunnableDismissingKeyguard(
- runnable = runnable,
- afterKeyguardGone = willLaunchResolverActivity,
- dismissShade = collapse,
- willAnimateOnKeyguard = animate,
- )
- }
- } else {
- postOnUiThread(delay = 0, runnable)
- }
- }
-
- /** Starts an Activity. */
- fun startActivity(
- intent: Intent,
- dismissShade: Boolean = false,
- animationController: ActivityTransitionAnimator.Controller? = null,
- showOverLockscreenWhenLocked: Boolean = false,
- userHandle: UserHandle? = null,
- ) {
- val userHandle = userHandle ?: getActivityUserHandle(intent)
- // Make sure that we dismiss the keyguard if it is directly dismissible or when we don't
- // want to show the activity above it.
- if (keyguardStateController.isUnlocked || !showOverLockscreenWhenLocked) {
- startActivityDismissingKeyguard(
- intent = intent,
- onlyProvisioned = false,
- dismissShade = dismissShade,
- disallowEnterPictureInPictureWhileLaunching = false,
- callback = null,
- flags = 0,
- animationController = animationController,
- userHandle = userHandle,
- )
- return
- }
-
- val animate =
- animationController != null &&
- shouldAnimateLaunch(
- /* isActivityIntent= */ true,
- showOverLockscreenWhenLocked
- ) == true
-
- var controller: ActivityTransitionAnimator.Controller? = null
- if (animate) {
- // Wrap the animation controller to dismiss the shade and set
- // mIsLaunchingActivityOverLockscreen during the animation.
- val delegate =
- wrapAnimationControllerForShadeOrStatusBar(
- animationController = animationController,
- dismissShade = dismissShade,
- isLaunchForActivity = true,
- )
- controller = wrapAnimationControllerForLockscreen(delegate)
- } else if (dismissShade) {
- // The animation will take care of dismissing the shade at the end of the animation.
- // If we don't animate, collapse it directly.
- shadeControllerLazy.get().cancelExpansionAndCollapseShade()
- }
-
- // We should exit the dream to prevent the activity from starting below the
- // dream.
- if (keyguardUpdateMonitor.isDreaming) {
- centralSurfaces?.awakenDreams()
- }
-
- activityTransitionAnimator.startIntentWithAnimation(
- controller,
- animate,
- intent.getPackage(),
- showOverLockscreenWhenLocked
- ) { adapter: RemoteAnimationAdapter? ->
- TaskStackBuilder.create(context)
- .addNextIntent(intent)
- .startActivities(
- CentralSurfaces.getActivityOptions(displayId, adapter),
- userHandle
- )
- }
- }
-
- /** Executes an action after dismissing keyguard. */
- fun dismissKeyguardThenExecute(
- action: OnDismissAction,
- cancel: Runnable? = null,
- afterKeyguardGone: Boolean = false,
- customMessage: String? = null,
- ) {
- if (
- !action.willRunAnimationOnKeyguard() &&
- wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP &&
- keyguardStateController.canDismissLockScreen() &&
- !statusBarStateController.leaveOpenOnKeyguardHide() &&
- dozeServiceHostLazy.get().isPulsing
- ) {
- // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a
- // pulse.
- // TODO: Factor this transition out of BiometricUnlockController.
- biometricUnlockControllerLazy
- .get()
- .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
- }
- if (keyguardStateController.isShowing) {
- statusBarKeyguardViewManagerLazy
- .get()
- .dismissWithAction(action, cancel, afterKeyguardGone, customMessage)
- } else {
- // If the keyguard isn't showing but the device is dreaming, we should exit the
- // dream.
- if (keyguardUpdateMonitor.isDreaming) {
- centralSurfaces?.awakenDreams()
- }
- action.onDismiss()
- }
- }
-
- /** Executes an action after dismissing keyguard. */
- fun executeRunnableDismissingKeyguard(
- runnable: Runnable? = null,
- cancelAction: Runnable? = null,
- dismissShade: Boolean = false,
- afterKeyguardGone: Boolean = false,
- deferred: Boolean = false,
- willAnimateOnKeyguard: Boolean = false,
- customMessage: String? = null,
- ) {
- val onDismissAction: OnDismissAction =
- object : OnDismissAction {
- override fun onDismiss(): Boolean {
- if (runnable != null) {
- if (
- keyguardStateController.isShowing &&
- keyguardStateController.isOccluded
- ) {
- statusBarKeyguardViewManagerLazy
- .get()
- .addAfterKeyguardGoneRunnable(runnable)
- } else {
- mainExecutor.execute(runnable)
- }
- }
- if (dismissShade) {
- shadeControllerLazy.get().collapseShadeForActivityStart()
- }
- return deferred
- }
-
- override fun willRunAnimationOnKeyguard(): Boolean {
- return willAnimateOnKeyguard
- }
- }
- dismissKeyguardThenExecute(
- onDismissAction,
- cancelAction,
- afterKeyguardGone,
- customMessage,
- )
- }
-
- /**
- * Return a [ActivityTransitionAnimator.Controller] wrapping `animationController` so that:
- * - if it launches in the notification shade window and `dismissShade` is true, then the
- * shade will be instantly dismissed at the end of the animation.
- * - if it launches in status bar window, it will make the status bar window match the
- * device size during the animation (that way, the animation won't be clipped by the
- * status bar size).
- *
- * @param animationController the controller that is wrapped and will drive the main
- * animation.
- * @param dismissShade whether the notification shade will be dismissed at the end of the
- * animation. This is ignored if `animationController` is not animating in the shade
- * window.
- * @param isLaunchForActivity whether the launch is for an activity.
- */
- private fun wrapAnimationControllerForShadeOrStatusBar(
- animationController: ActivityTransitionAnimator.Controller?,
- dismissShade: Boolean,
- isLaunchForActivity: Boolean,
- ): ActivityTransitionAnimator.Controller? {
- if (animationController == null) {
- return null
- }
- val rootView = animationController.transitionContainer.rootView
- val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> =
- statusBarWindowController.wrapAnimationControllerIfInStatusBar(
- rootView,
- animationController
- )
- if (controllerFromStatusBar.isPresent) {
- return controllerFromStatusBar.get()
- }
-
- centralSurfaces?.let {
- // If the view is not in the status bar, then we are animating a view in the shade.
- // We have to make sure that we collapse it when the animation ends or is cancelled.
- if (dismissShade) {
- return StatusBarTransitionAnimatorController(
- animationController,
- shadeAnimationInteractor,
- shadeControllerLazy.get(),
- notifShadeWindowControllerLazy.get(),
- commandQueue,
- displayId,
- isLaunchForActivity
- )
- }
- }
-
- return animationController
- }
-
- /**
- * Wraps an animation controller so that if an activity would be launched on top of the
- * lockscreen, the correct flags are set for it to be occluded.
- */
- private fun wrapAnimationControllerForLockscreen(
- animationController: ActivityTransitionAnimator.Controller?
- ): ActivityTransitionAnimator.Controller? {
- return animationController?.let {
- object : DelegateTransitionAnimatorController(it) {
- override fun onIntentStarted(willAnimate: Boolean) {
- delegate.onIntentStarted(willAnimate)
- if (willAnimate) {
- centralSurfaces?.setIsLaunchingActivityOverLockscreen(true)
- }
- }
-
- override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
- super.onTransitionAnimationStart(isExpandingFullyAbove)
-
- // Double check that the keyguard is still showing and not going
- // away, but if so set the keyguard occluded. Typically, WM will let
- // KeyguardViewMediator know directly, but we're overriding that to
- // play the custom launch animation, so we need to take care of that
- // here. The unocclude animation is not overridden, so WM will call
- // KeyguardViewMediator's unocclude animation runner when the
- // activity is exited.
- if (
- keyguardStateController.isShowing &&
- !keyguardStateController.isKeyguardGoingAway
- ) {
- Log.d(TAG, "Setting occluded = true in #startActivity.")
- keyguardViewMediatorLazy
- .get()
- .setOccluded(true /* isOccluded */, true /* animate */)
- }
- }
-
- override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
- // Set mIsLaunchingActivityOverLockscreen to false before actually
- // finishing the animation so that we can assume that
- // mIsLaunchingActivityOverLockscreen being true means that we will
- // collapse the shade (or at least run the post collapse runnables)
- // later on.
- centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
- delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
- }
-
- override fun onTransitionAnimationCancelled(
- newKeyguardOccludedState: Boolean?
- ) {
- if (newKeyguardOccludedState != null) {
- keyguardViewMediatorLazy
- .get()
- .setOccluded(newKeyguardOccludedState, false /* animate */)
- }
-
- // Set mIsLaunchingActivityOverLockscreen to false before actually
- // finishing the animation so that we can assume that
- // mIsLaunchingActivityOverLockscreen being true means that we will
- // collapse the shade (or at least run the // post collapse
- // runnables) later on.
- centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
- delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
- }
- }
- }
- }
-
- /** Retrieves the current user handle to start the Activity. */
- private fun getActivityUserHandle(intent: Intent): UserHandle {
- val packages: Array<String> =
- context.resources.getStringArray(R.array.system_ui_packages)
- for (pkg in packages) {
- val componentName = intent.component ?: break
- if (pkg == componentName.packageName) {
- return UserHandle(UserHandle.myUserId())
- }
- }
- return userTracker.userHandle
- }
+ private fun postOnUiThread(delay: Int = 0, runnable: Runnable) {
+ mainExecutor.executeDelayed(runnable, delay.toLong())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
new file mode 100644
index 000000000000..e8443982d560
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.Bundle
+import android.os.UserHandle
+import android.view.View
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.plugins.ActivityStarter
+
+interface ActivityStarterInternal {
+ /**
+ * Starts a pending intent after dismissing keyguard.
+ *
+ * This can be called in a background thread (to prevent calls in [ActivityIntentHelper] in the
+ * main thread).
+ */
+ fun startPendingIntentDismissingKeyguard(
+ intent: PendingIntent,
+ intentSentUiThreadCallback: Runnable? = null,
+ associatedView: View? = null,
+ animationController: ActivityTransitionAnimator.Controller? = null,
+ showOverLockscreen: Boolean = false,
+ fillInIntent: Intent? = null,
+ extraOptions: Bundle? = null,
+ )
+
+ /** Starts an activity after dismissing keyguard. */
+ fun startActivityDismissingKeyguard(
+ intent: Intent,
+ dismissShade: Boolean,
+ onlyProvisioned: Boolean = false,
+ callback: ActivityStarter.Callback? = null,
+ flags: Int = 0,
+ animationController: ActivityTransitionAnimator.Controller? = null,
+ customMessage: String? = null,
+ disallowEnterPictureInPictureWhileLaunching: Boolean = false,
+ userHandle: UserHandle? = null,
+ )
+
+ /** Starts an Activity. */
+ fun startActivity(
+ intent: Intent,
+ dismissShade: Boolean,
+ animationController: ActivityTransitionAnimator.Controller?,
+ showOverLockscreenWhenLocked: Boolean,
+ userHandle: UserHandle? = null,
+ )
+
+ /** Executes an action after dismissing keyguard. */
+ fun dismissKeyguardThenExecute(
+ action: ActivityStarter.OnDismissAction,
+ cancel: Runnable?,
+ afterKeyguardGone: Boolean,
+ customMessage: String? = null,
+ )
+
+ /** Executes an action after dismissing keyguard. */
+ fun executeRunnableDismissingKeyguard(
+ runnable: Runnable?,
+ cancelAction: Runnable? = null,
+ dismissShade: Boolean = false,
+ afterKeyguardGone: Boolean = false,
+ deferred: Boolean = false,
+ willAnimateOnKeyguard: Boolean = false,
+ customMessage: String? = null,
+ )
+
+ /** Whether we should animate an activity launch. */
+ fun shouldAnimateLaunch(isActivityIntent: Boolean): Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
new file mode 100644
index 000000000000..c101755bcf38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.Bundle
+import android.os.UserHandle
+import android.view.View
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import javax.inject.Inject
+
+/**
+ * Encapsulates the activity logic for activity starter when flexiglass is enabled.
+ *
+ * TODO: b/308819693
+ */
+@SysUISingleton
+class ActivityStarterInternalImpl @Inject constructor() : ActivityStarterInternal {
+ override fun startPendingIntentDismissingKeyguard(
+ intent: PendingIntent,
+ intentSentUiThreadCallback: Runnable?,
+ associatedView: View?,
+ animationController: ActivityTransitionAnimator.Controller?,
+ showOverLockscreen: Boolean,
+ fillInIntent: Intent?,
+ extraOptions: Bundle?
+ ) {
+ TODO("Not yet implemented b/308819693")
+ }
+
+ override fun startActivityDismissingKeyguard(
+ intent: Intent,
+ dismissShade: Boolean,
+ onlyProvisioned: Boolean,
+ callback: ActivityStarter.Callback?,
+ flags: Int,
+ animationController: ActivityTransitionAnimator.Controller?,
+ customMessage: String?,
+ disallowEnterPictureInPictureWhileLaunching: Boolean,
+ userHandle: UserHandle?
+ ) {
+ TODO("Not yet implemented b/308819693")
+ }
+
+ override fun startActivity(
+ intent: Intent,
+ dismissShade: Boolean,
+ animationController: ActivityTransitionAnimator.Controller?,
+ showOverLockscreenWhenLocked: Boolean,
+ userHandle: UserHandle?
+ ) {
+ TODO("Not yet implemented b/308819693")
+ }
+
+ override fun dismissKeyguardThenExecute(
+ action: ActivityStarter.OnDismissAction,
+ cancel: Runnable?,
+ afterKeyguardGone: Boolean,
+ customMessage: String?
+ ) {
+ TODO("Not yet implemented b/308819693")
+ }
+
+ override fun executeRunnableDismissingKeyguard(
+ runnable: Runnable?,
+ cancelAction: Runnable?,
+ dismissShade: Boolean,
+ afterKeyguardGone: Boolean,
+ deferred: Boolean,
+ willAnimateOnKeyguard: Boolean,
+ customMessage: String?
+ ) {
+ TODO("Not yet implemented b/308819693")
+ }
+
+ override fun shouldAnimateLaunch(isActivityIntent: Boolean): Boolean {
+ TODO("Not yet implemented b/308819693")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index a1fae03a2090..2651d2eed404 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -103,8 +103,8 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {
/**
* Returns an ActivityOptions bundle created using the given parameters.
*
- * @param displayId The ID of the display to launch the activity in. Typically this would
- * be the display the status bar is on.
+ * @param displayId The ID of the display to launch the activity in. Typically this would
+ * be the display the status bar is on.
* @param animationAdapter The animation adapter used to start this activity, or {@code null}
* for the default animation.
*/
@@ -197,7 +197,7 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {
void onKeyguardViewManagerStatesUpdated();
- /** */
+ /** */
boolean getCommandQueuePanelsEnabled();
void showWirelessChargingAnimation(int batteryLevel);
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 5baf6a069602..1d6b744a7bf0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -168,6 +168,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -594,6 +595,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final SceneContainerFlags mSceneContainerFlags;
+ private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor;
+
/**
* Public constructor for CentralSurfaces.
*
@@ -705,7 +708,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
UserTracker userTracker,
Provider<FingerprintManager> fingerprintManager,
ActivityStarter activityStarter,
- SceneContainerFlags sceneContainerFlags
+ SceneContainerFlags sceneContainerFlags,
+ BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor
) {
mContext = context;
mNotificationsController = notificationsController;
@@ -801,6 +805,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mFingerprintManager = fingerprintManager;
mActivityStarter = activityStarter;
mSceneContainerFlags = sceneContainerFlags;
+ mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -1083,6 +1088,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mJavaAdapter.alwaysCollectFlow(
mCommunalInteractor.isIdleOnCommunal(),
mIdleOnCommunalConsumer);
+ if (mSceneContainerFlags.isEnabled()) {
+ mJavaAdapter.alwaysCollectFlow(
+ mBrightnessMirrorShowingInteractor.isShowing(),
+ this::setBrightnessMirrorShowing
+ );
+ }
}
/**
@@ -1284,10 +1295,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mShadeSurface,
mNotificationShadeDepthControllerLazy.get(),
mBrightnessSliderFactory,
- (visible) -> {
- mBrightnessMirrorVisible = visible;
- updateScrimController();
- });
+ this::setBrightnessMirrorShowing);
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragmentLegacy) {
@@ -1346,6 +1354,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f));
}
+ private void setBrightnessMirrorShowing(boolean showing) {
+ mBrightnessMirrorVisible = showing;
+ updateScrimController();
+ }
/**
* When swiping up to dismiss the lock screen, the panel expansion fraction goes from 1f to 0f.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 495b4e1e14cd..4c3c7d56df50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -421,9 +422,12 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
public void updateHeader(NotificationEntry entry) {
ExpandableNotificationRow row = entry.getRow();
float headerVisibleAmount = 1.0f;
- if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild
- || row.showingPulsing()) {
- headerVisibleAmount = mAppearFraction;
+ // To fix the invisible HUN group header issue
+ if (!AsyncGroupHeaderViewInflation.isEnabled()) {
+ if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild
+ || row.showingPulsing()) {
+ headerVisibleAmount = mAppearFraction;
+ }
}
row.setHeaderVisibleAmount(headerVisibleAmount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
new file mode 100644
index 000000000000..ebaeb39efe31
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.app.ActivityManager
+import android.app.ActivityOptions
+import android.app.ActivityTaskManager
+import android.app.PendingIntent
+import android.app.TaskStackBuilder
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.RemoteException
+import android.os.UserHandle
+import android.provider.Settings
+import android.util.Log
+import android.view.RemoteAnimationAdapter
+import android.view.View
+import android.view.WindowManager
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.DelegateTransitionAnimatorController
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.camera.CameraIntents
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.kotlin.getOrNull
+import dagger.Lazy
+import java.util.Optional
+import javax.inject.Inject
+
+/** Encapsulates the activity logic for activity starter. */
+@SysUISingleton
+class LegacyActivityStarterInternalImpl
+@Inject
+constructor(
+ private val centralSurfacesOptLazy: Lazy<Optional<CentralSurfaces>>,
+ private val keyguardStateController: KeyguardStateController,
+ private val statusBarStateController: SysuiStatusBarStateController,
+ private val assistManagerLazy: Lazy<AssistManager>,
+ private val dozeServiceHostLazy: Lazy<DozeServiceHost>,
+ private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
+ private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
+ private val shadeControllerLazy: Lazy<ShadeController>,
+ private val commandQueue: CommandQueue,
+ private val shadeAnimationInteractor: ShadeAnimationInteractor,
+ private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>,
+ private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
+ private val activityTransitionAnimator: ActivityTransitionAnimator,
+ private val context: Context,
+ @DisplayId private val displayId: Int,
+ private val lockScreenUserManager: NotificationLockscreenUserManager,
+ private val statusBarWindowController: StatusBarWindowController,
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val deviceProvisionedController: DeviceProvisionedController,
+ private val userTracker: UserTracker,
+ private val activityIntentHelper: ActivityIntentHelper,
+ @Main private val mainExecutor: DelayableExecutor,
+) : ActivityStarterInternal {
+ private val centralSurfaces: CentralSurfaces?
+ get() = centralSurfacesOptLazy.get().getOrNull()
+
+ override fun startActivityDismissingKeyguard(
+ intent: Intent,
+ dismissShade: Boolean,
+ onlyProvisioned: Boolean,
+ callback: ActivityStarter.Callback?,
+ flags: Int,
+ animationController: ActivityTransitionAnimator.Controller?,
+ customMessage: String?,
+ disallowEnterPictureInPictureWhileLaunching: Boolean,
+ userHandle: UserHandle?,
+ ) {
+ val userHandle: UserHandle = userHandle ?: getActivityUserHandle(intent)
+
+ if (onlyProvisioned && !deviceProvisionedController.isDeviceProvisioned) return
+
+ val willLaunchResolverActivity: Boolean =
+ activityIntentHelper.wouldLaunchResolverActivity(
+ intent,
+ lockScreenUserManager.currentUserId
+ )
+
+ val animate =
+ animationController != null &&
+ !willLaunchResolverActivity &&
+ shouldAnimateLaunch(isActivityIntent = true)
+ val animController =
+ wrapAnimationControllerForShadeOrStatusBar(
+ animationController = animationController,
+ dismissShade = dismissShade,
+ isLaunchForActivity = true,
+ )
+
+ // If we animate, we will dismiss the shade only once the animation is done. This is
+ // taken care of by the StatusBarLaunchAnimationController.
+ val dismissShadeDirectly = dismissShade && animController == null
+
+ val runnable = Runnable {
+ assistManagerLazy.get().hideAssist()
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ intent.addFlags(flags)
+ val result = intArrayOf(ActivityManager.START_CANCELED)
+ activityTransitionAnimator.startIntentWithAnimation(
+ animController,
+ animate,
+ intent.getPackage()
+ ) { adapter: RemoteAnimationAdapter? ->
+ val options =
+ ActivityOptions(CentralSurfaces.getActivityOptions(displayId, adapter))
+
+ // We know that the intent of the caller is to dismiss the keyguard and
+ // this runnable is called right after the keyguard is solved, so we tell
+ // WM that we should dismiss it to avoid flickers when opening an activity
+ // that can also be shown over the keyguard.
+ options.setDismissKeyguardIfInsecure()
+ options.setDisallowEnterPictureInPictureWhileLaunching(
+ disallowEnterPictureInPictureWhileLaunching
+ )
+ if (CameraIntents.isInsecureCameraIntent(intent)) {
+ // Normally an activity will set it's requested rotation
+ // animation on its window. However when launching an activity
+ // causes the orientation to change this is too late. In these cases
+ // the default animation is used. This doesn't look good for
+ // the camera (as it rotates the camera contents out of sync
+ // with physical reality). So, we ask the WindowManager to
+ // force the cross fade animation if an orientation change
+ // happens to occur during the launch.
+ options.rotationAnimationHint =
+ WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+ }
+ if (Settings.Panel.ACTION_VOLUME == intent.action) {
+ // Settings Panel is implemented as activity(not a dialog), so
+ // underlying app is paused and may enter picture-in-picture mode
+ // as a result.
+ // So we need to disable picture-in-picture mode here
+ // if it is volume panel.
+ options.setDisallowEnterPictureInPictureWhileLaunching(true)
+ }
+ try {
+ result[0] =
+ ActivityTaskManager.getService()
+ .startActivityAsUser(
+ null,
+ context.basePackageName,
+ context.attributionTag,
+ intent,
+ intent.resolveTypeIfNeeded(context.contentResolver),
+ null,
+ null,
+ 0,
+ Intent.FLAG_ACTIVITY_NEW_TASK,
+ null,
+ options.toBundle(),
+ userHandle.identifier,
+ )
+ } catch (e: RemoteException) {
+ Log.w(TAG, "Unable to start activity", e)
+ }
+ result[0]
+ }
+ callback?.onActivityStarted(result[0])
+ }
+ val cancelRunnable = Runnable {
+ callback?.onActivityStarted(ActivityManager.START_CANCELED)
+ }
+ // Do not deferKeyguard when occluded because, when keyguard is occluded,
+ // we do not launch the activity until keyguard is done.
+ val occluded = (keyguardStateController.isShowing && keyguardStateController.isOccluded)
+ val deferred = !occluded
+ executeRunnableDismissingKeyguard(
+ runnable,
+ cancelRunnable,
+ dismissShadeDirectly,
+ willLaunchResolverActivity,
+ deferred,
+ animate,
+ customMessage,
+ )
+ }
+
+ override fun startPendingIntentDismissingKeyguard(
+ intent: PendingIntent,
+ intentSentUiThreadCallback: Runnable?,
+ associatedView: View?,
+ animationController: ActivityTransitionAnimator.Controller?,
+ showOverLockscreen: Boolean,
+ fillInIntent: Intent?,
+ extraOptions: Bundle?,
+ ) {
+ val animationController =
+ if (associatedView is ExpandableNotificationRow) {
+ centralSurfaces?.getAnimatorControllerFromNotification(associatedView)
+ } else animationController
+
+ val willLaunchResolverActivity =
+ (intent.isActivity &&
+ activityIntentHelper.wouldPendingLaunchResolverActivity(
+ intent,
+ lockScreenUserManager.currentUserId,
+ ))
+
+ val actuallyShowOverLockscreen =
+ showOverLockscreen &&
+ intent.isActivity &&
+ activityIntentHelper.wouldPendingShowOverLockscreen(
+ intent,
+ lockScreenUserManager.currentUserId
+ )
+
+ val animate =
+ !willLaunchResolverActivity &&
+ animationController != null &&
+ shouldAnimateLaunch(intent.isActivity, actuallyShowOverLockscreen)
+
+ // We wrap animationCallback with a StatusBarLaunchAnimatorController so
+ // that the shade is collapsed after the animation (or when it is cancelled,
+ // aborted, etc).
+ val statusBarController =
+ wrapAnimationControllerForShadeOrStatusBar(
+ animationController = animationController,
+ dismissShade = true,
+ isLaunchForActivity = intent.isActivity,
+ )
+ val controller =
+ if (actuallyShowOverLockscreen) {
+ wrapAnimationControllerForLockscreen(statusBarController)
+ } else {
+ statusBarController
+ }
+
+ // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
+ // run the animation on the keyguard). The animation will take care of (instantly)
+ // collapsing the shade and hiding the keyguard once it is done.
+ val collapse = !animate
+ val runnable = Runnable {
+ try {
+ activityTransitionAnimator.startPendingIntentWithAnimation(
+ controller,
+ animate,
+ intent.creatorPackage,
+ actuallyShowOverLockscreen,
+ object : ActivityTransitionAnimator.PendingIntentStarter {
+ override fun startPendingIntent(
+ animationAdapter: RemoteAnimationAdapter?
+ ): Int {
+ val options =
+ ActivityOptions(
+ CentralSurfaces.getActivityOptions(displayId, animationAdapter)
+ .apply { extraOptions?.let { putAll(it) } }
+ )
+ // TODO b/221255671: restrict this to only be set for
+ // notifications
+ options.isEligibleForLegacyPermissionPrompt = true
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ return intent.sendAndReturnResult(
+ context,
+ 0,
+ fillInIntent,
+ null,
+ null,
+ null,
+ options.toBundle()
+ )
+ }
+ },
+ )
+ } catch (e: PendingIntent.CanceledException) {
+ // the stack trace isn't very helpful here.
+ // Just log the exception message.
+ Log.w(TAG, "Sending intent failed: $e")
+ if (!collapse) {
+ // executeRunnableDismissingKeyguard did not collapse for us already.
+ shadeControllerLazy.get().collapseOnMainThread()
+ }
+ // TODO: Dismiss Keyguard.
+ }
+ if (intent.isActivity) {
+ assistManagerLazy.get().hideAssist()
+ // This activity could have started while the device is dreaming, in which case
+ // the dream would occlude the activity. In order to show the newly started
+ // activity, we wake from the dream.
+ keyguardUpdateMonitor.awakenFromDream()
+ }
+ intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
+ }
+
+ if (!actuallyShowOverLockscreen) {
+ postOnUiThread(delay = 0) {
+ executeRunnableDismissingKeyguard(
+ runnable = runnable,
+ afterKeyguardGone = willLaunchResolverActivity,
+ dismissShade = collapse,
+ willAnimateOnKeyguard = animate,
+ )
+ }
+ } else {
+ postOnUiThread(delay = 0, runnable)
+ }
+ }
+
+ override fun startActivity(
+ intent: Intent,
+ dismissShade: Boolean,
+ animationController: ActivityTransitionAnimator.Controller?,
+ showOverLockscreenWhenLocked: Boolean,
+ userHandle: UserHandle?,
+ ) {
+ val userHandle = userHandle ?: getActivityUserHandle(intent)
+ // Make sure that we dismiss the keyguard if it is directly dismissible or when we don't
+ // want to show the activity above it.
+ if (keyguardStateController.isUnlocked || !showOverLockscreenWhenLocked) {
+ startActivityDismissingKeyguard(
+ intent = intent,
+ onlyProvisioned = false,
+ dismissShade = dismissShade,
+ disallowEnterPictureInPictureWhileLaunching = false,
+ callback = null,
+ flags = 0,
+ animationController = animationController,
+ userHandle = userHandle,
+ )
+ return
+ }
+
+ val animate =
+ animationController != null &&
+ shouldAnimateLaunch(/* isActivityIntent= */ true, showOverLockscreenWhenLocked)
+
+ var controller: ActivityTransitionAnimator.Controller? = null
+ if (animate) {
+ // Wrap the animation controller to dismiss the shade and set
+ // mIsLaunchingActivityOverLockscreen during the animation.
+ val delegate =
+ wrapAnimationControllerForShadeOrStatusBar(
+ animationController = animationController,
+ dismissShade = dismissShade,
+ isLaunchForActivity = true,
+ )
+ controller = wrapAnimationControllerForLockscreen(delegate)
+ } else if (dismissShade) {
+ // The animation will take care of dismissing the shade at the end of the animation.
+ // If we don't animate, collapse it directly.
+ shadeControllerLazy.get().cancelExpansionAndCollapseShade()
+ }
+
+ // We should exit the dream to prevent the activity from starting below the
+ // dream.
+ if (keyguardUpdateMonitor.isDreaming) {
+ centralSurfaces?.awakenDreams()
+ }
+
+ activityTransitionAnimator.startIntentWithAnimation(
+ controller,
+ animate,
+ intent.getPackage(),
+ showOverLockscreenWhenLocked
+ ) { adapter: RemoteAnimationAdapter? ->
+ TaskStackBuilder.create(context)
+ .addNextIntent(intent)
+ .startActivities(CentralSurfaces.getActivityOptions(displayId, adapter), userHandle)
+ }
+ }
+
+ override fun dismissKeyguardThenExecute(
+ action: ActivityStarter.OnDismissAction,
+ cancel: Runnable?,
+ afterKeyguardGone: Boolean,
+ customMessage: String?,
+ ) {
+ if (
+ !action.willRunAnimationOnKeyguard() &&
+ wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP &&
+ keyguardStateController.canDismissLockScreen() &&
+ !statusBarStateController.leaveOpenOnKeyguardHide() &&
+ dozeServiceHostLazy.get().isPulsing
+ ) {
+ // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a
+ // pulse.
+ // TODO: Factor this transition out of BiometricUnlockController.
+ biometricUnlockControllerLazy
+ .get()
+ .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+ }
+ if (keyguardStateController.isShowing) {
+ statusBarKeyguardViewManagerLazy
+ .get()
+ .dismissWithAction(action, cancel, afterKeyguardGone, customMessage)
+ } else {
+ // If the keyguard isn't showing but the device is dreaming, we should exit the
+ // dream.
+ if (keyguardUpdateMonitor.isDreaming) {
+ centralSurfaces?.awakenDreams()
+ }
+ action.onDismiss()
+ }
+ }
+
+ override fun executeRunnableDismissingKeyguard(
+ runnable: Runnable?,
+ cancelAction: Runnable?,
+ dismissShade: Boolean,
+ afterKeyguardGone: Boolean,
+ deferred: Boolean,
+ willAnimateOnKeyguard: Boolean,
+ customMessage: String?,
+ ) {
+ val onDismissAction: ActivityStarter.OnDismissAction =
+ object : ActivityStarter.OnDismissAction {
+ override fun onDismiss(): Boolean {
+ if (runnable != null) {
+ if (
+ keyguardStateController.isShowing && keyguardStateController.isOccluded
+ ) {
+ statusBarKeyguardViewManagerLazy
+ .get()
+ .addAfterKeyguardGoneRunnable(runnable)
+ } else {
+ mainExecutor.execute(runnable)
+ }
+ }
+ if (dismissShade) {
+ shadeControllerLazy.get().collapseShadeForActivityStart()
+ }
+ return deferred
+ }
+
+ override fun willRunAnimationOnKeyguard(): Boolean {
+ return willAnimateOnKeyguard
+ }
+ }
+ dismissKeyguardThenExecute(
+ onDismissAction,
+ cancelAction,
+ afterKeyguardGone,
+ customMessage,
+ )
+ }
+
+ /**
+ * Return a [ActivityTransitionAnimator.Controller] wrapping `animationController` so that:
+ * - if it launches in the notification shade window and `dismissShade` is true, then the shade
+ * will be instantly dismissed at the end of the animation.
+ * - if it launches in status bar window, it will make the status bar window match the device
+ * size during the animation (that way, the animation won't be clipped by the status bar
+ * size).
+ *
+ * @param animationController the controller that is wrapped and will drive the main animation.
+ * @param dismissShade whether the notification shade will be dismissed at the end of the
+ * animation. This is ignored if `animationController` is not animating in the shade window.
+ * @param isLaunchForActivity whether the launch is for an activity.
+ */
+ private fun wrapAnimationControllerForShadeOrStatusBar(
+ animationController: ActivityTransitionAnimator.Controller?,
+ dismissShade: Boolean,
+ isLaunchForActivity: Boolean,
+ ): ActivityTransitionAnimator.Controller? {
+ if (animationController == null) {
+ return null
+ }
+ val rootView = animationController.transitionContainer.rootView
+ val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> =
+ statusBarWindowController.wrapAnimationControllerIfInStatusBar(
+ rootView,
+ animationController
+ )
+ if (controllerFromStatusBar.isPresent) {
+ return controllerFromStatusBar.get()
+ }
+
+ centralSurfaces?.let {
+ // If the view is not in the status bar, then we are animating a view in the shade.
+ // We have to make sure that we collapse it when the animation ends or is cancelled.
+ if (dismissShade) {
+ return StatusBarTransitionAnimatorController(
+ animationController,
+ shadeAnimationInteractor,
+ shadeControllerLazy.get(),
+ notifShadeWindowControllerLazy.get(),
+ commandQueue,
+ displayId,
+ isLaunchForActivity
+ )
+ }
+ }
+
+ return animationController
+ }
+
+ /**
+ * Wraps an animation controller so that if an activity would be launched on top of the
+ * lockscreen, the correct flags are set for it to be occluded.
+ */
+ private fun wrapAnimationControllerForLockscreen(
+ animationController: ActivityTransitionAnimator.Controller?
+ ): ActivityTransitionAnimator.Controller? {
+ return animationController?.let {
+ object : DelegateTransitionAnimatorController(it) {
+ override fun onIntentStarted(willAnimate: Boolean) {
+ delegate.onIntentStarted(willAnimate)
+ if (willAnimate) {
+ centralSurfaces?.setIsLaunchingActivityOverLockscreen(true)
+ }
+ }
+
+ override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+ super.onTransitionAnimationStart(isExpandingFullyAbove)
+
+ // Double check that the keyguard is still showing and not going
+ // away, but if so set the keyguard occluded. Typically, WM will let
+ // KeyguardViewMediator know directly, but we're overriding that to
+ // play the custom launch animation, so we need to take care of that
+ // here. The unocclude animation is not overridden, so WM will call
+ // KeyguardViewMediator's unocclude animation runner when the
+ // activity is exited.
+ if (
+ keyguardStateController.isShowing &&
+ !keyguardStateController.isKeyguardGoingAway
+ ) {
+ Log.d(TAG, "Setting occluded = true in #startActivity.")
+ keyguardViewMediatorLazy
+ .get()
+ .setOccluded(true /* isOccluded */, true /* animate */)
+ }
+ }
+
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ // Set mIsLaunchingActivityOverLockscreen to false before actually
+ // finishing the animation so that we can assume that
+ // mIsLaunchingActivityOverLockscreen being true means that we will
+ // collapse the shade (or at least run the post collapse runnables)
+ // later on.
+ centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
+ delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
+ }
+
+ override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ if (newKeyguardOccludedState != null) {
+ keyguardViewMediatorLazy
+ .get()
+ .setOccluded(newKeyguardOccludedState, false /* animate */)
+ }
+
+ // Set mIsLaunchingActivityOverLockscreen to false before actually
+ // finishing the animation so that we can assume that
+ // mIsLaunchingActivityOverLockscreen being true means that we will
+ // collapse the shade (or at least run the // post collapse
+ // runnables) later on.
+ centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
+ delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
+ }
+ }
+ }
+ }
+
+ /** Retrieves the current user handle to start the Activity. */
+ private fun getActivityUserHandle(intent: Intent): UserHandle {
+ val packages: Array<String> = context.resources.getStringArray(R.array.system_ui_packages)
+ for (pkg in packages) {
+ val componentName = intent.component ?: break
+ if (pkg == componentName.packageName) {
+ return UserHandle(UserHandle.myUserId())
+ }
+ }
+ return userTracker.userHandle
+ }
+
+ /**
+ * Whether we should animate an activity launch.
+ *
+ * Note: This method must be called *before* dismissing the keyguard.
+ */
+ private fun shouldAnimateLaunch(
+ isActivityIntent: Boolean,
+ showOverLockscreen: Boolean,
+ ): Boolean {
+ // TODO(b/294418322): Support launch animations when occluded.
+ if (keyguardStateController.isOccluded) {
+ return false
+ }
+
+ // Always animate if we are not showing the keyguard or if we animate over the lockscreen
+ // (without unlocking it).
+ if (showOverLockscreen || !keyguardStateController.isShowing) {
+ return true
+ }
+
+ // We don't animate non-activity launches as they can break the animation.
+ // TODO(b/184121838): Support non activity launches on the lockscreen.
+ return isActivityIntent
+ }
+
+ override fun shouldAnimateLaunch(isActivityIntent: Boolean): Boolean {
+ return shouldAnimateLaunch(isActivityIntent, false)
+ }
+
+ private fun postOnUiThread(delay: Int = 0, runnable: Runnable) {
+ mainExecutor.executeDelayed(runnable, delay.toLong())
+ }
+
+ companion object {
+ private const val TAG = "LegacyActivityStarterInternalImpl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 5f26702ad867..077f330ff3b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -67,12 +67,12 @@ import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
+import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent;
import com.android.systemui.keyguard.shared.model.DismissAction;
import com.android.systemui.keyguard.shared.model.KeyguardDone;
import com.android.systemui.keyguard.shared.model.KeyguardState;
@@ -381,7 +381,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
Lazy<ShadeController> shadeController,
LatencyTracker latencyTracker,
KeyguardSecurityModel keyguardSecurityModel,
- FeatureFlags featureFlags,
PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor,
PrimaryBouncerInteractor primaryBouncerInteractor,
BouncerView primaryBouncerView,
@@ -413,7 +412,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mShadeController = shadeController;
mLatencyTracker = latencyTracker;
mKeyguardSecurityModel = keyguardSecurityModel;
- mFlags = featureFlags;
mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mPrimaryBouncerView = primaryBouncerView;
@@ -805,7 +803,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
boolean afterKeyguardGone, String message) {
- if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (RefactorKeyguardDismissIntent.isEnabled()) {
if (r == null) {
return;
}
@@ -857,7 +855,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
return;
}
- if (!mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (!RefactorKeyguardDismissIntent.isEnabled()) {
mAfterKeyguardGoneAction = r;
mKeyguardGoneCancelAction = cancelAction;
mDismissActionWillAnimateOnKeyguard = r != null
@@ -925,7 +923,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* Adds a {@param runnable} to be executed after Keyguard is gone.
*/
public void addAfterKeyguardGoneRunnable(Runnable runnable) {
- if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (RefactorKeyguardDismissIntent.isEnabled()) {
if (runnable != null) {
mKeyguardDismissActionInteractor.get().runAfterKeyguardGone(runnable);
}
@@ -1118,7 +1116,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
// We update the state (which will show the keyguard) only if an animation will run on
// the keyguard. If there is no animation, we wait before updating the state so that we
// go directly from bouncer to launcher/app.
- if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (RefactorKeyguardDismissIntent.isEnabled()) {
if (mKeyguardDismissActionInteractor.get().runDismissAnimationOnKeyguard()) {
updateStates();
}
@@ -1245,7 +1243,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
private void executeAfterKeyguardGoneAction() {
- if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (RefactorKeyguardDismissIntent.isEnabled()) {
return;
}
if (mAfterKeyguardGoneAction != null) {
@@ -1634,7 +1632,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
pw.println(" bouncerIsOrWillBeShowing(): " + primaryBouncerIsOrWillBeShowing());
pw.println(" Registered KeyguardViewManagerCallbacks:");
pw.println(" refactorKeyguardDismissIntent enabled:"
- + mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT));
+ + RefactorKeyguardDismissIntent.isEnabled());
for (KeyguardViewManagerCallback callback : mCallbacks) {
pw.println(" " + callback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 13f76feca9fd..7d920eab73fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -27,6 +27,7 @@ import android.widget.FrameLayout;
import com.android.systemui.res.R;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.settings.brightness.ToggleSlider;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeViewController;
@@ -38,8 +39,7 @@ import java.util.function.Consumer;
/**
* Controls showing and hiding of the brightness mirror.
*/
-public class BrightnessMirrorController
- implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> {
+public class BrightnessMirrorController implements MirrorController {
private final NotificationShadeWindowView mStatusBarWindow;
private final Consumer<Boolean> mVisibilityCallback;
@@ -71,6 +71,7 @@ public class BrightnessMirrorController
updateResources();
}
+ @Override
public void showMirror() {
mBrightnessMirror.setVisibility(View.VISIBLE);
mVisibilityCallback.accept(true);
@@ -78,16 +79,14 @@ public class BrightnessMirrorController
mDepthController.setBrightnessMirrorVisible(true);
}
+ @Override
public void hideMirror() {
mVisibilityCallback.accept(false);
mNotificationPanel.setAlpha(255, true /* animate */);
mDepthController.setBrightnessMirrorVisible(false);
}
- /**
- * Set the location and size of the mirror container to match that of the slider in QS
- * @param original the original view in QS
- */
+ @Override
public void setLocationAndSize(View original) {
original.getLocationInWindow(mInt2Cache);
@@ -112,6 +111,7 @@ public class BrightnessMirrorController
}
}
+ @Override
public ToggleSlider getToggleSlider() {
return mToggleSliderController;
}
@@ -176,8 +176,4 @@ public class BrightnessMirrorController
public void onUiModeChanged() {
reinflate();
}
-
- public interface BrightnessMirrorListener {
- void onBrightnessMirrorReinflated(View brightnessMirror);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index a30660645990..11cbc9c66923 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -176,7 +176,7 @@ class HeadsUpManagerLogger @Inject constructor(
bool1 = alert
bool2 = hasEntry
}, {
- "request: update notification $str1 alert: $bool1 hasEntry: $bool2 reason: $str2"
+ "request: update notification $str1 alert: $bool1 hasEntry: $bool2"
})
}
@@ -186,7 +186,7 @@ class HeadsUpManagerLogger @Inject constructor(
bool1 = alert
bool2 = hasEntry
}, {
- "update notification $str1 alert: $bool1 hasEntry: $bool2 reason: $str2"
+ "update notification $str1 alert: $bool1 hasEntry: $bool2"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/UiEventLoggerStartableModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/UiEventLoggerStartableModule.kt
new file mode 100644
index 000000000000..9b84090d72cd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/UiEventLoggerStartableModule.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.volume.dagger
+
+import com.android.systemui.volume.domain.startable.AudioModeLoggerStartable
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+interface UiEventLoggerStartableModule {
+
+ @Binds
+ @IntoSet
+ fun bindAudioModeLoggerStartable(
+ audioModeLoggerStartable: AudioModeLoggerStartable,
+ ): VolumePanelStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartable.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartable.kt
new file mode 100644
index 000000000000..12447577e945
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartable.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.domain.startable
+
+import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
+
+/** Logger for audio mode */
+@VolumePanelScope
+class AudioModeLoggerStartable
+@Inject
+constructor(
+ @VolumePanelScope private val scope: CoroutineScope,
+ private val uiEventLogger: UiEventLogger,
+ private val audioModeInteractor: AudioModeInteractor,
+) : VolumePanelStartable {
+
+ override fun start() {
+ scope.launch {
+ audioModeInteractor.isOngoingCall.distinctUntilChanged().collect { ongoingCall ->
+ uiEventLogger.log(
+ if (ongoingCall) VolumePanelUiEvent.VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_CALLING
+ else VolumePanelUiEvent.VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_NORMAL
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
index 8f18aa8021ae..8ce3b1fa1e73 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
@@ -41,12 +41,14 @@ import kotlinx.coroutines.flow.map
interface AncSliceRepository {
/**
- * ANC slice with a given width. Emits null when there is no ANC slice available. This can mean
- * that:
+ * ANC slice with a given width. [isCollapsed] slice shows a single button, and expanded shows a
+ * row buttons.
+ *
+ * Emits null when there is no ANC slice available. This can mean that:
* - there is no supported device connected;
* - there is no slice provider for the uri;
*/
- fun ancSlice(width: Int): Flow<Slice?>
+ fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?>
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -60,9 +62,14 @@ constructor(
private val localMediaRepository = mediaRepositoryFactory.create(null)
- override fun ancSlice(width: Int): Flow<Slice?> {
+ override fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?> {
return localMediaRepository.currentConnectedDevice
- .map { (it as? BluetoothMediaDevice)?.cachedDevice?.device?.getExtraControlUri(width) }
+ .map {
+ (it as? BluetoothMediaDevice)
+ ?.cachedDevice
+ ?.device
+ ?.getExtraControlUri(width, isCollapsed, hideLabel)
+ }
.distinctUntilChanged()
.flatMapLatest { sliceUri ->
sliceUri ?: return@flatMapLatest flowOf(null)
@@ -71,7 +78,11 @@ constructor(
.flowOn(backgroundCoroutineContext)
}
- private fun BluetoothDevice.getExtraControlUri(width: Int): Uri? {
+ private fun BluetoothDevice.getExtraControlUri(
+ width: Int,
+ isCollapsed: Boolean,
+ hideLabel: Boolean
+ ): Uri? {
val uri: String? = BluetoothUtils.getControlUriMetaData(this)
uri ?: return null
@@ -81,7 +92,8 @@ constructor(
Uri.parse(
"$uri$width" +
"&version=${SliceParameters.VERSION}" +
- "&is_collapsed=${SliceParameters.IS_COLLAPSED}"
+ "&is_collapsed=$isCollapsed" +
+ "&hide_label=$hideLabel"
)
}
}
@@ -98,11 +110,5 @@ constructor(
* 2) new slice
*/
const val VERSION = 2
-
- /**
- * Collapsed slice shows a single button, and expanded shows a row buttons. Supported since
- * [VERSION]==2.
- */
- const val IS_COLLAPSED = false
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt
index 89b927480783..dc4be2696204 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.anc.domain
import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor
+import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import javax.inject.Inject
@@ -31,5 +32,6 @@ constructor(
private val ancSliceInteractor: AncSliceInteractor,
) : ComponentAvailabilityCriteria {
- override fun isAvailable(): Flow<Boolean> = ancSliceInteractor.ancSlice.map { it != null }
+ override fun isAvailable(): Flow<Boolean> =
+ ancSliceInteractor.ancSlices.map { it is AncSlices.Ready }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt
index 91af622074a0..cefa26907710 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt
@@ -20,16 +20,19 @@ import android.app.slice.Slice.HINT_ERROR
import android.app.slice.SliceItem.FORMAT_SLICE
import androidx.slice.Slice
import com.android.systemui.volume.panel.component.anc.data.repository.AncSliceRepository
+import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
/** Provides a valid slice from [AncSliceRepository]. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -41,25 +44,35 @@ constructor(
scope: CoroutineScope,
) {
- // Start with a positive width to check is the Slice is available.
- private val width = MutableStateFlow(1)
+ // Any positive width to check if the Slice is available.
+ private val buttonSliceWidth = MutableStateFlow(1)
+ private val popupSliceWidth = MutableStateFlow(1)
- /** Provides a valid ANC slice. */
- val ancSlice: SharedFlow<Slice?> =
- width
- .flatMapLatest { width -> ancSliceRepository.ancSlice(width) }
- .map { slice ->
- if (slice?.isValidSlice() == true) {
- slice
+ val ancSlices: StateFlow<AncSlices> =
+ combine(
+ buttonSliceWidth.flatMapLatest {
+ ancSlice(width = it, isCollapsed = true, hideLabel = true)
+ },
+ popupSliceWidth.flatMapLatest {
+ ancSlice(width = it, isCollapsed = false, hideLabel = false)
+ }
+ ) { buttonSlice, popupSlice ->
+ if (buttonSlice != null && popupSlice != null) {
+ AncSlices.Ready(buttonSlice = buttonSlice, popupSlice = popupSlice)
} else {
- null
+ AncSlices.Unavailable
}
}
- .shareIn(scope, SharingStarted.Eagerly, replay = 1)
+ .stateIn(scope, SharingStarted.Eagerly, AncSlices.Unavailable)
- /** Updates the width of the [ancSlice] */
- fun changeWidth(newWidth: Int) {
- width.value = newWidth
+ /**
+ * Provides a valid [isCollapsed] ANC slice for a given [width]. Use [hideLabel] == true to
+ * remove the labels from the [Slice].
+ */
+ private fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?> {
+ return ancSliceRepository
+ .ancSlice(width = width, isCollapsed = isCollapsed, hideLabel = hideLabel)
+ .filter { it?.isValidSlice() != false }
}
private fun Slice.isValidSlice(): Boolean {
@@ -73,4 +86,20 @@ constructor(
}
return false
}
+
+ /**
+ * Call this to update [AncSlices.Ready.popupSlice] width in a reaction to container size
+ * change.
+ */
+ fun onPopupSliceWidthChanged(width: Int) {
+ popupSliceWidth.tryEmit(width)
+ }
+
+ /**
+ * Call this to update [AncSlices.Ready.buttonSlice] width in a reaction to container size
+ * change.
+ */
+ fun onButtonSliceWidthChanged(width: Int) {
+ buttonSliceWidth.tryEmit(width)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/model/AncSlices.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/model/AncSlices.kt
new file mode 100644
index 000000000000..3cd4e672ce56
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/model/AncSlices.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.domain.model
+
+import androidx.slice.Slice
+
+/** Modes current ANC slices state */
+sealed interface AncSlices {
+
+ data class Ready(
+ val popupSlice: Slice,
+ val buttonSlice: Slice,
+ ) : AncSlices
+
+ /** Couldn't one or both slices. */
+ data object Unavailable : AncSlices
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
index eb96f6cad8f2..bee79bb68141 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
@@ -16,52 +16,56 @@
package com.android.systemui.volume.panel.component.anc.ui.viewmodel
-import android.content.Context
import androidx.slice.Slice
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.anc.domain.AncAvailabilityCriteria
import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor
-import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
+import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Volume Panel ANC component view model. */
+@OptIn(ExperimentalCoroutinesApi::class)
@VolumePanelScope
class AncViewModel
@Inject
constructor(
- @Application private val context: Context,
@VolumePanelScope private val coroutineScope: CoroutineScope,
private val interactor: AncSliceInteractor,
+ private val availabilityCriteria: AncAvailabilityCriteria,
) {
+ val isAvailable: Flow<Boolean>
+ get() = availabilityCriteria.isAvailable()
+
/** ANC [Slice]. Null when there is no slice available for ANC. */
- val slice: StateFlow<Slice?> =
- interactor.ancSlice.stateIn(coroutineScope, SharingStarted.Eagerly, null)
+ val popupSlice: StateFlow<Slice?> =
+ interactor.ancSlices
+ .filterIsInstance<AncSlices.Ready>()
+ .map { it.popupSlice }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
- /**
- * ButtonViewModel to be shown in the VolumePanel. Null when there is no ANC Slice available.
- */
- val button: StateFlow<ButtonViewModel?> =
- interactor.ancSlice
- .map { slice ->
- slice?.let {
- ButtonViewModel(
- Icon.Resource(R.drawable.ic_noise_aware, null),
- context.getString(R.string.volume_panel_noise_control_title)
- )
- }
- }
+ /** Button [Slice] to be shown in the VolumePanel. Null when there is no ANC Slice available. */
+ val buttonSlice: StateFlow<Slice?> =
+ interactor.ancSlices
+ .filterIsInstance<AncSlices.Ready>()
+ .map { it.buttonSlice }
.stateIn(coroutineScope, SharingStarted.Eagerly, null)
- /** Call this to update [slice] width in a reaction to container size change. */
- fun changeSliceWidth(width: Int) {
- interactor.changeWidth(width)
+ /** Call this to update [popupSlice] width in a reaction to container size change. */
+ fun onPopupSliceWidthChanged(width: Int) {
+ interactor.onPopupSliceWidthChanged(width)
+ }
+
+ /** Call this to update [buttonSlice] width in a reaction to container size change. */
+ fun onButtonSliceWidthChanged(width: Int) {
+ interactor.onButtonSliceWidthChanged(width)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
index 04d7b1fa6532..3ca9cdfe285c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
@@ -18,8 +18,10 @@ package com.android.systemui.volume.panel.component.bottombar.ui.viewmodel
import android.content.Intent
import android.provider.Settings
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import javax.inject.Inject
@@ -29,6 +31,7 @@ class BottomBarViewModel
constructor(
private val activityStarter: ActivityStarter,
private val volumePanelViewModel: VolumePanelViewModel,
+ private val uiEventLogger: UiEventLogger,
) {
fun onDoneClicked() {
@@ -36,6 +39,7 @@ constructor(
}
fun onSettingsClicked() {
+ uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_SOUND_SETTINGS_CLICKED)
activityStarter.startActivityDismissingKeyguard(
/* intent = */ Intent(Settings.ACTION_SOUND_SETTINGS),
/* onlyProvisioned = */ false,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
index aab825fb9f5e..85da1d0efe3a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
@@ -16,18 +16,36 @@
package com.android.systemui.volume.panel.component.captioning.domain
+import com.android.internal.logging.UiEventLogger
import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
@VolumePanelScope
class CaptioningAvailabilityCriteria
@Inject
-constructor(private val captioningInteractor: CaptioningInteractor) :
- ComponentAvailabilityCriteria {
+constructor(
+ captioningInteractor: CaptioningInteractor,
+ @VolumePanelScope private val scope: CoroutineScope,
+ private val uiEventLogger: UiEventLogger,
+) : ComponentAvailabilityCriteria {
- override fun isAvailable(): Flow<Boolean> =
+ private val availability =
captioningInteractor.isSystemAudioCaptioningUiEnabled
+ .onEach { visible ->
+ uiEventLogger.log(
+ if (visible) VolumePanelUiEvent.VOLUME_PANEL_LIVE_CAPTION_TOGGLE_SHOWN
+ else VolumePanelUiEvent.VOLUME_PANEL_LIVE_CAPTION_TOGGLE_GONE
+ )
+ }
+ .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
+
+ override fun isAvailable(): Flow<Boolean> = availability
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
index 92f8f221d918..01421f86311f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
@@ -17,11 +17,13 @@
package com.android.systemui.volume.panel.component.captioning.ui.viewmodel
import android.content.Context
+import com.android.internal.logging.UiEventLogger
import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -38,6 +40,7 @@ constructor(
private val context: Context,
private val captioningInteractor: CaptioningInteractor,
@VolumePanelScope private val coroutineScope: CoroutineScope,
+ private val uiEventLogger: UiEventLogger,
) {
val buttonViewModel: StateFlow<ToggleButtonViewModel?> =
@@ -57,6 +60,13 @@ constructor(
.stateIn(coroutineScope, SharingStarted.Eagerly, null)
fun setIsSystemAudioCaptioningEnabled(enabled: Boolean) {
+ uiEventLogger.logWithPosition(
+ VolumePanelUiEvent.VOLUME_PANEL_LIVE_CAPTION_TOGGLE_CLICKED,
+ 0,
+ null,
+ if (enabled) VolumePanelUiEvent.LIVE_CAPTION_TOGGLE_ENABLED
+ else VolumePanelUiEvent.LIVE_CAPTION_TOGGLE_DISABLED
+ )
coroutineScope.launch { captioningInteractor.setIsSystemAudioCaptioningEnabled(enabled) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index fc9602e6017f..6b237f8e329b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel
import android.content.Context
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
@@ -26,6 +27,7 @@ import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlayback
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -48,6 +50,7 @@ constructor(
private val actionsInteractor: MediaOutputActionsInteractor,
private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
interactor: MediaOutputInteractor,
+ private val uiEventLogger: UiEventLogger,
) {
private val sessionWithPlayback: StateFlow<SessionWithPlayback?> =
@@ -126,6 +129,7 @@ constructor(
)
fun onBarClick(expandable: Expandable) {
+ uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
actionsInteractor.onBarClick(sessionWithPlayback.value, expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
index f022039e9cde..4ecdd46163f9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.spatial.ui.viewmodel
import android.content.Context
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
@@ -29,6 +30,7 @@ import com.android.systemui.volume.panel.component.spatial.domain.interactor.Spa
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -46,6 +48,7 @@ constructor(
@VolumePanelScope private val scope: CoroutineScope,
availabilityCriteria: SpatialAudioAvailabilityCriteria,
private val interactor: SpatialAudioComponentInteractor,
+ private val uiEventLogger: UiEventLogger,
) {
val spatialAudioButton: StateFlow<ButtonViewModel?> =
@@ -101,6 +104,19 @@ constructor(
.stateIn(scope, SharingStarted.Eagerly, emptyList())
fun setEnabled(model: SpatialAudioEnabledModel) {
+ uiEventLogger.logWithPosition(
+ VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_TOGGLE_CLICKED,
+ 0,
+ null,
+ when (model) {
+ SpatialAudioEnabledModel.Disabled -> 0
+ SpatialAudioEnabledModel.SpatialAudioEnabled -> 1
+ SpatialAudioEnabledModel.HeadTrackingEnabled -> 2
+ else -> {
+ -1
+ }
+ }
+ )
scope.launch { interactor.setEnabled(model) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
deleted file mode 100644
index ecd89eab1d4b..000000000000
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
+++ /dev/null
@@ -1,47 +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.panel.component.volume.domain.interactor
-
-import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
-import javax.inject.Inject
-
-/** Converts from slider value to volume and back. */
-@VolumePanelScope
-class VolumeSliderInteractor @Inject constructor() {
-
- /** mimic percentage volume setting */
- private val displayValueRange: ClosedFloatingPointRange<Float> = 0f..100f
-
- /**
- * Translates [volume], that belongs to [volumeRange] to the value that belongs to
- * [displayValueRange].
- */
- fun processVolumeToValue(
- volume: Int,
- volumeRange: ClosedRange<Int>,
- ): Float {
- val currentRangeStart: Float = volumeRange.start.toFloat()
- val targetRangeStart: Float = displayValueRange.start
- val currentRangeLength: Float = (volumeRange.endInclusive.toFloat() - currentRangeStart)
- val targetRangeLength: Float = displayValueRange.endInclusive - targetRangeStart
- if (currentRangeLength == 0f || targetRangeLength == 0f) {
- return 0f
- }
- val volumeFraction: Float = (volume.toFloat() - currentRangeStart) / currentRangeLength
- return targetRangeStart + volumeFraction * targetRangeLength
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 57b5d570fbbd..c8cd6fdbea70 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -18,13 +18,14 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
import android.content.Context
import android.media.AudioManager
+import com.android.internal.logging.UiEventLogger
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
-import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -44,7 +45,7 @@ constructor(
@Assisted private val coroutineScope: CoroutineScope,
private val context: Context,
private val audioVolumeInteractor: AudioVolumeInteractor,
- private val volumeSliderInteractor: VolumeSliderInteractor,
+ private val uiEventLogger: UiEventLogger,
) : SliderViewModel {
private val audioStream = audioStreamWrapper.audioStream
@@ -71,6 +72,19 @@ constructor(
AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm_unavailable,
AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable,
)
+ private val uiEventByStream =
+ mapOf(
+ AudioStream(AudioManager.STREAM_MUSIC) to
+ VolumePanelUiEvent.VOLUME_PANEL_MUSIC_SLIDER_TOUCHED,
+ AudioStream(AudioManager.STREAM_VOICE_CALL) to
+ VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
+ AudioStream(AudioManager.STREAM_RING) to
+ VolumePanelUiEvent.VOLUME_PANEL_RING_SLIDER_TOUCHED,
+ AudioStream(AudioManager.STREAM_NOTIFICATION) to
+ VolumePanelUiEvent.VOLUME_PANEL_NOTIFICATION_SLIDER_TOUCHED,
+ AudioStream(AudioManager.STREAM_ALARM) to
+ VolumePanelUiEvent.VOLUME_PANEL_ALARM_SLIDER_TOUCHED,
+ )
override val slider: StateFlow<SliderState> =
combine(
@@ -90,6 +104,10 @@ constructor(
}
}
+ override fun onValueChangeFinished() {
+ uiEventByStream[audioStream]?.let { uiEventLogger.log(it) }
+ }
+
override fun toggleMuted(state: SliderState) {
val audioViewModel = state as? State
audioViewModel ?: return
@@ -105,10 +123,6 @@ constructor(
return State(
value = volume.toFloat(),
valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
- valueText =
- SliderViewModel.formatValue(
- volumeSliderInteractor.processVolumeToValue(volume, volumeRange)
- ),
icon = getIcon(ringerMode),
label = labelsByStream[audioStream]?.let(context::getString)
?: error("No label for the stream: $audioStream"),
@@ -157,7 +171,6 @@ constructor(
override val valueRange: ClosedFloatingPointRange<Float>,
override val icon: Icon,
override val label: String,
- override val valueText: String,
override val disabledMessage: String?,
override val isEnabled: Boolean,
override val a11yStep: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index 8d8fa17bf986..956ab66ac0c3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -22,7 +22,6 @@ import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
-import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -41,7 +40,6 @@ constructor(
@Assisted private val coroutineScope: CoroutineScope,
private val context: Context,
private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
- private val volumeSliderInteractor: VolumeSliderInteractor,
) : SliderViewModel {
override val slider: StateFlow<SliderState> =
@@ -56,6 +54,8 @@ constructor(
}
}
+ override fun onValueChangeFinished() {}
+
override fun toggleMuted(state: SliderState) {
// do nothing because this action isn't supported for Cast sliders.
}
@@ -66,13 +66,6 @@ constructor(
value = currentVolume.toFloat(),
valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
icon = Icon.Resource(R.drawable.ic_cast, null),
- valueText =
- SliderViewModel.formatValue(
- volumeSliderInteractor.processVolumeToValue(
- volume = currentVolume,
- volumeRange = volumeRange,
- )
- ),
label = context.getString(R.string.media_device_cast),
isEnabled = true,
a11yStep = 1
@@ -83,13 +76,13 @@ constructor(
override val value: Float,
override val valueRange: ClosedFloatingPointRange<Float>,
override val icon: Icon,
- override val valueText: String,
override val label: String,
override val isEnabled: Boolean,
override val a11yStep: Int,
) : SliderState {
override val disabledMessage: String?
get() = null
+
override val isMutable: Boolean
get() = false
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
index 8eb0b8947c37..d71a9d8dae94 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
@@ -28,7 +28,6 @@ sealed interface SliderState {
val valueRange: ClosedFloatingPointRange<Float>
val icon: Icon?
val isEnabled: Boolean
- val valueText: String
val label: String
/**
* A11y slider controls works by adjusting one step up or down. The default slider step isn't
@@ -42,7 +41,6 @@ sealed interface SliderState {
override val value: Float = 0f
override val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
override val icon: Icon? = null
- override val valueText: String = ""
override val label: String = ""
override val disabledMessage: String? = null
override val a11yStep: Int = 0
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
index e78f833086f8..7ded8c5c9fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
@@ -25,10 +25,7 @@ interface SliderViewModel {
fun onValueChanged(state: SliderState, newValue: Float)
- fun toggleMuted(state: SliderState)
-
- companion object {
+ fun onValueChangeFinished()
- fun formatValue(value: Float): String = "%.0f".format(value)
- }
+ fun toggleMuted(state: SliderState)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
index d1d539003f93..f889ed6e06be 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.dagger
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
import dagger.Module
@@ -31,4 +32,6 @@ interface DefaultMultibindsModule {
@Multibinds fun criteriaMap(): Map<VolumePanelComponentKey, ComponentAvailabilityCriteria>
@Multibinds fun components(): Map<VolumePanelComponentKey, VolumePanelUiComponent>
+
+ @Multibinds fun startables(): Set<VolumePanelStartable>
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index d868c33d0887..ec64f3d93012 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.panel.dagger
+import com.android.systemui.volume.dagger.UiEventLoggerStartableModule
import com.android.systemui.volume.panel.component.anc.AncModule
import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
import com.android.systemui.volume.panel.component.captioning.CaptioningModule
@@ -25,6 +26,7 @@ import com.android.systemui.volume.panel.component.volume.VolumeSlidersModule
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.DomainModule
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
import com.android.systemui.volume.panel.ui.UiModule
import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
@@ -47,6 +49,7 @@ import kotlinx.coroutines.CoroutineScope
DefaultMultibindsModule::class,
DomainModule::class,
UiModule::class,
+ UiEventLoggerStartableModule::class,
// Components modules
BottomBarModule::class,
AncModule::class,
@@ -66,6 +69,8 @@ interface VolumePanelComponent {
fun componentsLayoutManager(): ComponentsLayoutManager
+ fun volumePanelStartables(): Set<VolumePanelStartable>
+
@Subcomponent.Factory
interface Factory : VolumePanelComponentFactory {
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/VolumePanelStartable.kt
index be1f081d5b8f..9c39f5e75f88 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/VolumePanelStartable.kt
@@ -1,13 +1,11 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * 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
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -15,8 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
--->
-<resources>
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">false</bool>
-</resources>
+
+package com.android.systemui.volume.panel.domain
+
+/** Code that needs to be run when Volume Panel is started.. */
+interface VolumePanelStartable {
+ fun start()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt
new file mode 100644
index 000000000000..8b8714fcca8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt
@@ -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 com.android.systemui.volume.panel.ui
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+/** UI events for Volume Panel. */
+enum class VolumePanelUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The volume panel is shown") VOLUME_PANEL_SHOWN(1634),
+ @UiEvent(doc = "The volume panel is gone") VOLUME_PANEL_GONE(1635),
+ @UiEvent(doc = "Media output is clicked") VOLUME_PANEL_MEDIA_OUTPUT_CLICKED(1636),
+ @UiEvent(doc = "Audio mode changed to normal") VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_NORMAL(1680),
+ @UiEvent(doc = "Audio mode changed to calling") VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_CALLING(1681),
+ @UiEvent(doc = "Sound settings is clicked") VOLUME_PANEL_SOUND_SETTINGS_CLICKED(1638),
+ @UiEvent(doc = "The music volume slider is touched") VOLUME_PANEL_MUSIC_SLIDER_TOUCHED(1639),
+ @UiEvent(doc = "The voice call volume slider is touched")
+ VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED(1640),
+ @UiEvent(doc = "The ring volume slider is touched") VOLUME_PANEL_RING_SLIDER_TOUCHED(1641),
+ @UiEvent(doc = "The notification volume slider is touched")
+ VOLUME_PANEL_NOTIFICATION_SLIDER_TOUCHED(1642),
+ @UiEvent(doc = "The alarm volume slider is touched") VOLUME_PANEL_ALARM_SLIDER_TOUCHED(1643),
+ @UiEvent(doc = "Live caption toggle is shown") VOLUME_PANEL_LIVE_CAPTION_TOGGLE_SHOWN(1644),
+ @UiEvent(doc = "Live caption toggle is gone") VOLUME_PANEL_LIVE_CAPTION_TOGGLE_GONE(1645),
+ @UiEvent(doc = "Live caption toggle is clicked") VOLUME_PANEL_LIVE_CAPTION_TOGGLE_CLICKED(1646),
+ @UiEvent(doc = "Spatial audio button is shown") VOLUME_PANEL_SPATIAL_AUDIO_BUTTON_SHOWN(1647),
+ @UiEvent(doc = "Spatial audio button is gone") VOLUME_PANEL_SPATIAL_AUDIO_BUTTON_GONE(1648),
+ @UiEvent(doc = "Spatial audio popup is shown") VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN(1649),
+ @UiEvent(doc = "Spatial audio toggle is clicked")
+ VOLUME_PANEL_SPATIAL_AUDIO_TOGGLE_CLICKED(1650),
+ @UiEvent(doc = "ANC button is shown") VOLUME_PANEL_ANC_BUTTON_SHOWN(1651),
+ @UiEvent(doc = "ANC button is gone") VOLUME_PANEL_ANC_BUTTON_GONE(1652),
+ @UiEvent(doc = "ANC popup is shown") VOLUME_PANEL_ANC_POPUP_SHOWN(1653),
+ @UiEvent(doc = "ANC toggle is clicked") VOLUME_PANEL_ANC_TOGGLE_CLICKED(1654);
+
+ override fun getId() = metricId
+
+ companion object {
+ const val LIVE_CAPTION_TOGGLE_DISABLED = 0
+ const val LIVE_CAPTION_TOGGLE_ENABLED = 1
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
index c728fefa77e6..ccb91ac79b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
@@ -21,8 +21,10 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import javax.inject.Inject
@@ -34,6 +36,7 @@ constructor(
private val volumePanelViewModelFactory: Provider<VolumePanelViewModel.Factory>,
private val volumePanelFlag: VolumePanelFlag,
private val configurationController: ConfigurationController,
+ private val uiEventLogger: UiEventLogger,
) : ComponentActivity() {
private val viewModel: VolumePanelViewModel by
@@ -43,8 +46,16 @@ constructor(
enableEdgeToEdge()
super.onCreate(savedInstanceState)
volumePanelFlag.assertNewVolumePanel()
-
- setContent { VolumePanelRoot(viewModel = viewModel, onDismiss = ::finish) }
+ uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_SHOWN)
+ setContent {
+ VolumePanelRoot(
+ viewModel = viewModel,
+ onDismiss = {
+ uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_GONE)
+ finish()
+ }
+ )
+ }
}
override fun onContentChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
index 5ae827ff4e3d..1de4fd1f9593 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
@@ -26,6 +26,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.onConfigChanged
import com.android.systemui.volume.panel.dagger.VolumePanelComponent
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
@@ -109,6 +110,10 @@ class VolumePanelViewModel(
replay = 1,
)
+ init {
+ volumePanelComponent.volumePanelStartables().onEach(VolumePanelStartable::start)
+ }
+
fun dismissPanel() {
mutablePanelVisibility.update { false }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index d00081e0f595..1568e8c011a1 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -20,6 +20,9 @@ import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
import static android.app.WallpaperManager.SetWallpaperFlags;
+import static com.android.window.flags.Flags.offloadColorExtraction;
+
+import android.annotation.Nullable;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.Context;
@@ -135,6 +138,12 @@ public class ImageWallpaper extends WallpaperService {
mLongExecutor,
mLock,
new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
+
+ @Override
+ public void onColorsProcessed() {
+ CanvasEngine.this.notifyColorsChanged();
+ }
+
@Override
public void onColorsProcessed(List<RectF> regions,
List<WallpaperColors> colors) {
@@ -433,6 +442,12 @@ public class ImageWallpaper extends WallpaperService {
}
@Override
+ public @Nullable WallpaperColors onComputeColors() {
+ if (!offloadColorExtraction()) return null;
+ return mWallpaperLocalColorExtractor.onComputeColors();
+ }
+
+ @Override
public boolean supportsLocalColorExtraction() {
return true;
}
@@ -469,6 +484,12 @@ public class ImageWallpaper extends WallpaperService {
}
@Override
+ public void onDimAmountChanged(float dimAmount) {
+ if (!offloadColorExtraction()) return;
+ mWallpaperLocalColorExtractor.onDimAmountChanged(dimAmount);
+ }
+
+ @Override
public void onDisplayAdded(int displayId) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
index e2ec8dc056ca..d37dfb49c5e5 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
@@ -17,6 +17,8 @@
package com.android.systemui.wallpapers;
+import static com.android.window.flags.Flags.offloadColorExtraction;
+
import android.app.WallpaperColors;
import android.graphics.Bitmap;
import android.graphics.Rect;
@@ -66,6 +68,12 @@ public class WallpaperLocalColorExtractor {
private final List<RectF> mPendingRegions = new ArrayList<>();
private final Set<RectF> mProcessedRegions = new ArraySet<>();
+ private float mWallpaperDimAmount = 0f;
+ private WallpaperColors mWallpaperColors;
+
+ // By default we assume that colors were loaded from disk and don't need to be recomputed
+ private boolean mRecomputeColors = false;
+
@LongRunning
private final Executor mLongExecutor;
@@ -75,6 +83,12 @@ public class WallpaperLocalColorExtractor {
* Interface to handle the callbacks after the different steps of the color extraction
*/
public interface WallpaperLocalColorExtractorCallback {
+
+ /**
+ * Callback after the wallpaper colors have been computed
+ */
+ void onColorsProcessed();
+
/**
* Callback after the colors of new regions have been extracted
* @param regions the list of new regions that have been processed
@@ -129,7 +143,7 @@ public class WallpaperLocalColorExtractor {
if (displayWidth == mDisplayWidth && displayHeight == mDisplayHeight) return;
mDisplayWidth = displayWidth;
mDisplayHeight = displayHeight;
- processColorsInternal();
+ processLocalColorsInternal();
}
}
@@ -166,7 +180,8 @@ public class WallpaperLocalColorExtractor {
mBitmapHeight = bitmap.getHeight();
mMiniBitmap = createMiniBitmap(bitmap);
mWallpaperLocalColorExtractorCallback.onMiniBitmapUpdated();
- recomputeColors();
+ if (offloadColorExtraction() && mRecomputeColors) recomputeColorsInternal();
+ recomputeLocalColors();
}
}
@@ -184,16 +199,66 @@ public class WallpaperLocalColorExtractor {
if (mPages == pages) return;
mPages = pages;
if (mMiniBitmap != null && !mMiniBitmap.isRecycled()) {
- recomputeColors();
+ recomputeLocalColors();
}
}
}
- // helper to recompute colors, to be called in synchronized methods
- private void recomputeColors() {
+ /**
+ * Should be called when the dim amount of the wallpaper changes, to recompute the colors
+ */
+ public void onDimAmountChanged(float dimAmount) {
+ mLongExecutor.execute(() -> onDimAmountChangedSynchronized(dimAmount));
+ }
+
+ private void onDimAmountChangedSynchronized(float dimAmount) {
+ synchronized (mLock) {
+ if (mWallpaperDimAmount == dimAmount) return;
+ mWallpaperDimAmount = dimAmount;
+ mRecomputeColors = true;
+ recomputeColorsInternal();
+ }
+ }
+
+ /**
+ * To be called by {@link ImageWallpaper.CanvasEngine#onComputeColors}. This will either
+ * return the current wallpaper colors, or if the bitmap is not yet loaded, return null and call
+ * {@link WallpaperLocalColorExtractorCallback#onColorsProcessed()} when the colors are ready.
+ */
+ public WallpaperColors onComputeColors() {
+ mLongExecutor.execute(this::onComputeColorsSynchronized);
+ return mWallpaperColors;
+ }
+
+ private void onComputeColorsSynchronized() {
+ synchronized (mLock) {
+ if (mRecomputeColors) return;
+ mRecomputeColors = true;
+ recomputeColorsInternal();
+ }
+ }
+
+ /**
+ * helper to recompute main colors, to be called in synchronized methods
+ */
+ private void recomputeColorsInternal() {
+ if (mMiniBitmap == null) return;
+ mWallpaperColors = getWallpaperColors(mMiniBitmap, mWallpaperDimAmount);
+ mWallpaperLocalColorExtractorCallback.onColorsProcessed();
+ }
+
+ @VisibleForTesting
+ WallpaperColors getWallpaperColors(@NonNull Bitmap bitmap, float dimAmount) {
+ return WallpaperColors.fromBitmap(bitmap, dimAmount);
+ }
+
+ /**
+ * helper to recompute local colors, to be called in synchronized methods
+ */
+ private void recomputeLocalColors() {
mPendingRegions.addAll(mProcessedRegions);
mProcessedRegions.clear();
- processColorsInternal();
+ processLocalColorsInternal();
}
/**
@@ -216,7 +281,7 @@ public class WallpaperLocalColorExtractor {
if (!wasActive && isActive()) {
mWallpaperLocalColorExtractorCallback.onActivated();
}
- processColorsInternal();
+ processLocalColorsInternal();
}
}
@@ -353,7 +418,7 @@ public class WallpaperLocalColorExtractor {
* then notify the callback with the resulting colors for these regions
* This method should only be called synchronously
*/
- private void processColorsInternal() {
+ private void processLocalColorsInternal() {
/*
* if the miniBitmap is not yet loaded, that means the onBitmapChanged has not yet been
* called, and thus the wallpaper is not yet loaded. In that case, exit, the function
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index ca55dd8ba5fa..46684dcac315 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -142,7 +142,6 @@ class ClockEventControllerTest : SysuiTestCase() {
context.resources,
context,
mainExecutor,
- IMMEDIATE,
bgExecutor,
clockBuffers,
withDeps.featureFlags,
@@ -320,9 +319,19 @@ class ClockEventControllerTest : SysuiTestCase() {
fun listenForDozeAmountTransition_updatesClockDozeAmount() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
- whenever(keyguardTransitionInteractor.lockscreenToAodTransition)
+ whenever(
+ keyguardTransitionInteractor.transition(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.AOD
+ )
+ )
.thenReturn(transitionStep)
- whenever(keyguardTransitionInteractor.aodToLockscreenTransition)
+ whenever(
+ keyguardTransitionInteractor.transition(
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN
+ )
+ )
.thenReturn(transitionStep)
val job = underTest.listenForDozeAmountTransition(this)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index fde45d34a4fd..5af0c1f9765d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -52,6 +52,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -157,7 +158,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.internal.util.reflection.FieldSetter;
@@ -809,6 +809,31 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ public void whenFaceAuthenticated_biometricAuthenticatedCallback_beforeUpdatingFpState() {
+ // GIVEN listening for UDFPS fingerprint
+ when(mAuthController.isUdfpsSupported()).thenReturn(true);
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+ keyguardIsVisible();
+ final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
+ mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel;
+
+ // WHEN face is authenticated
+ when(mFaceAuthInteractor.isAuthenticated()).thenReturn(true);
+ when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(true);
+ when(mFaceAuthInteractor.isLockedOut()).thenReturn(false);
+ mKeyguardUpdateMonitor.onFaceAuthenticated(0, true);
+ mTestableLooper.processAllMessages();
+
+ // THEN verify keyguardUpdateMonitorCallback receives an onAuthenticated callback
+ // before cancelling the fingerprint request
+ InOrder inOrder = inOrder(mTestCallback, fpCancel);
+ inOrder.verify(mTestCallback).onBiometricAuthenticated(
+ eq(0), eq(BiometricSourceType.FACE), eq(true));
+ inOrder.verify(fpCancel).cancel();
+ }
+
+ @Test
public void whenDetectFingerprint_biometricDetectCallback() {
ArgumentCaptor<FingerprintManager.FingerprintDetectionCallback> fpDetectCallbackCaptor =
ArgumentCaptor.forClass(FingerprintManager.FingerprintDetectionCallback.class);
@@ -2133,7 +2158,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
null /* trustGrantedMessages */);
// THEN onTrustChanged is called FIRST
- final InOrder inOrder = Mockito.inOrder(callback);
+ final InOrder inOrder = inOrder(callback);
inOrder.verify(callback).onTrustChanged(eq(mSelectedUserInteractor.getSelectedUserId()));
// AND THEN onTrustGrantedForCurrentUser callback called
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index c20367efa5da..d267ad449f45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -1274,6 +1274,28 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
@Test
+ public void delayedFaceSensorLocationChangesAddsFaceScanningOverlay() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
+ 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);
+ mScreenDecorations.start();
+ verifyFaceScanningViewExists(false); // face scanning view not added yet
+
+ // WHEN the sensor location is updated
+ mFaceScanningProviders = new ArrayList<>();
+ mFaceScanningProviders.add(mFaceScanningDecorProvider);
+ when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders);
+ when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(true);
+ final Point location = new Point();
+ mFakeFacePropertyRepository.setSensorLocation(location);
+ mScreenDecorations.onFaceSensorLocationChanged(location);
+ mExecutor.runAllReady();
+
+ // THEN the face scanning view is added
+ verifyFaceScanningViewExists(true);
+ }
+
+ @Test
public void testPrivacyDotShowingListenerWorkWellWithNullParameter() {
mPrivacyDotShowingListener.onPrivacyDotShown(null);
mPrivacyDotShowingListener.onPrivacyDotHidden(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 69cd592d8f50..e0764205c85a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -181,7 +181,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
throws RemoteException {
enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ verify(mSpyController, atLeast(2)).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -220,7 +220,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mWaitAnimationDuration, /* targetScale= */ 1.0f,
DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationCallback);
- verify(mSpyController).enableWindowMagnificationInternal(1.0f, DEFAULT_CENTER_X,
+ verify(mSpyController).updateWindowMagnificationInternal(1.0f, DEFAULT_CENTER_X,
DEFAULT_CENTER_Y, 0f, 0f);
verify(mAnimationCallback).onResult(true);
}
@@ -244,7 +244,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
advanceTimeBy(mWaitAnimationDuration);
});
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ verify(mSpyController, atLeast(2)).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -299,7 +299,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
advanceTimeBy(mWaitAnimationDuration);
});
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ verify(mSpyController, atLeast(2)).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -341,7 +341,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
advanceTimeBy(mWaitAnimationDuration);
});
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ verify(mSpyController, atLeast(2)).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -377,7 +377,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
advanceTimeBy(mWaitAnimationDuration);
});
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ verify(mSpyController, atLeast(2)).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -439,7 +439,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
enableWindowMagnificationAndWaitAnimating(
mWaitAnimationDuration, Float.NaN, Float.NaN, Float.NaN, mAnimationCallback2);
- verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
+ verify(mSpyController, never()).updateWindowMagnificationInternal(anyFloat(), anyFloat(),
anyFloat());
verify(mAnimationCallback).onResult(false);
verify(mAnimationCallback2).onResult(true);
@@ -479,7 +479,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
// Verify the method is called in
// {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
// {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
- verify(mSpyController, times(2)).enableWindowMagnificationInternal(
+ verify(mSpyController, times(2)).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -526,7 +526,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, Float.NaN,
Float.NaN, Float.NaN, mAnimationCallback2);
- verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
+ verify(mSpyController, never()).updateWindowMagnificationInternal(anyFloat(), anyFloat(),
anyFloat());
verify(mSpyController, never()).deleteWindowMagnification();
verify(mAnimationCallback).onResult(false);
@@ -551,7 +551,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
advanceTimeBy(mWaitAnimationDuration);
});
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ verify(mSpyController, atLeast(2)).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -720,7 +720,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
- verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
+ verify(mSpyController, never()).updateWindowMagnificationInternal(anyFloat(), anyFloat(),
anyFloat());
verify(mAnimationCallback).onResult(true);
}
@@ -733,7 +733,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
resetMockObjects();
deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ verify(mSpyController, atLeast(2)).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -790,7 +790,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
// Verify the method is called in
// {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
// {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
- verify(mSpyController, times(2)).enableWindowMagnificationInternal(
+ verify(mSpyController, times(2)).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -835,7 +835,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
// {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
// {@link Animator.AnimatorListener#onAnimationEnd} once when running the animation at
// the final duration time.
- verify(mSpyController, times(2)).enableWindowMagnificationInternal(
+ verify(mSpyController, times(2)).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -1040,17 +1040,17 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
}
@Override
- void enableWindowMagnificationInternal(float scale, float centerX, float centerY) {
- super.enableWindowMagnificationInternal(scale, centerX, centerY);
- mSpyController.enableWindowMagnificationInternal(scale, centerX, centerY);
+ void updateWindowMagnificationInternal(float scale, float centerX, float centerY) {
+ super.updateWindowMagnificationInternal(scale, centerX, centerY);
+ mSpyController.updateWindowMagnificationInternal(scale, centerX, centerY);
}
@Override
- void enableWindowMagnificationInternal(float scale, float centerX, float centerY,
+ void updateWindowMagnificationInternal(float scale, float centerX, float centerY,
float magnificationOffsetFrameRatioX, float magnificationOffsetFrameRatioY) {
- super.enableWindowMagnificationInternal(scale, centerX, centerY,
+ super.updateWindowMagnificationInternal(scale, centerX, centerY,
magnificationOffsetFrameRatioX, magnificationOffsetFrameRatioY);
- mSpyController.enableWindowMagnificationInternal(scale, centerX, centerY,
+ mSpyController.updateWindowMagnificationInternal(scale, centerX, centerY,
magnificationOffsetFrameRatioX, magnificationOffsetFrameRatioY);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 6f285fb01cf5..cb42078460c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -273,7 +273,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void enableWindowMagnification_showControlAndNotifyBoundsChanged() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
@@ -341,7 +341,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void enableWindowMagnification_systemGestureExclusionRectsIsSet() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
// Wait for Rects updated.
@@ -358,7 +358,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
@@ -373,7 +373,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() {
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN,
Float.NaN));
@@ -391,7 +391,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
setSystemGestureInsets();
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
bounds.bottom);
});
ReferenceTestUtils.waitForCondition(this::hasMagnificationOverlapFlag);
@@ -407,7 +407,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void deleteWindowMagnification_notifySourceBoundsChanged() {
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN,
Float.NaN));
@@ -423,7 +423,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void moveMagnifier_schedulesFrame() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.moveWindowMagnifier(100f, 100f);
});
@@ -506,7 +506,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void setScale_enabled_expectedValueAndUpdateStateDescription() {
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(2.0f,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(2.0f,
Float.NaN, Float.NaN));
mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
@@ -539,7 +539,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
final float displayWidth = windowBounds.width();
final PointF magnifiedCenter = new PointF(center, center + 5f);
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
magnifiedCenter.x, magnifiedCenter.y);
// Get the center again in case the center we set is out of screen.
magnifiedCenter.set(mWindowMagnificationController.getCenterX(),
@@ -582,7 +582,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
final float expectedRatio = 0.5f;
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
@@ -621,8 +621,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
preferredWindowSize.toString())
.commit();
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController
- .enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN);
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
});
// Change screen density and size to trigger restoring the preferred window size
@@ -649,7 +649,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
@@ -674,7 +674,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
Mockito.reset(mWindowManager);
Mockito.reset(mMirrorWindowControl);
@@ -703,7 +703,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void initializeA11yNode_enabled_expectedValues() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
Float.NaN);
});
final View mirrorView = mWindowManager.getAttachedView();
@@ -727,7 +727,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
public void performA11yActions_visible_expectedResults() {
final int displayId = mContext.getDisplayId();
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(1.5f, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(1.5f, Float.NaN,
Float.NaN);
});
@@ -761,7 +761,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
public void performA11yActions_visible_notifyAccessibilityActionPerformed() {
final int displayId = mContext.getDisplayId();
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
Float.NaN);
});
@@ -774,7 +774,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
});
@@ -812,7 +812,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
/* pbase= */ 1);
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -852,7 +852,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
/* pbase= */ 1);
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -888,7 +888,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
final int startingHeight = windowBounds.height();
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -908,7 +908,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
final int startingHeight = windowBounds.height();
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -931,7 +931,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
/* base= */ 1,
/* pbase= */ 1);
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingSize, startingSize);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -971,7 +971,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
/* pbase= */ 1);
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingSize, startingSize);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -1007,7 +1007,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
final int startingSize = mMinWindowSize;
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingSize, startingSize);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -1027,7 +1027,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
final int startingSize = mMinWindowSize;
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingSize, startingSize);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -1043,7 +1043,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void enableWindowMagnification_hasA11yWindowTitle() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
@@ -1054,12 +1054,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void enableWindowMagnificationWithScaleLessThanOne_enabled_disabled() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(0.9f, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(0.9f, Float.NaN,
Float.NaN);
});
@@ -1078,7 +1078,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
final int newRotation = simulateRotateTheDevice();
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN, Float.NaN));
assertEquals(newRotation, mWindowMagnificationController.mRotation);
@@ -1087,7 +1087,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void enableWindowMagnification_registerComponentCallback() {
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN,
Float.NaN));
@@ -1098,7 +1098,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
public void onLocaleChanged_enabled_updateA11yWindowTitle() {
final String newA11yWindowTitle = "new a11y window title";
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
final TestableResources testableResources = getContext().getOrCreateTestableResources();
@@ -1116,7 +1116,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void onSingleTap_enabled_scaleAnimates() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
@@ -1143,7 +1143,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
setSystemGestureInsets();
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
@@ -1161,7 +1161,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
setSystemGestureInsets();
mInstrumentation.runOnMainSync(
() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(
+ mWindowMagnificationController.updateWindowMagnificationInternal(
Float.NaN, Float.NaN, Float.NaN);
});
@@ -1197,7 +1197,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
setSystemGestureInsets();
mInstrumentation.runOnMainSync(
() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(
+ mWindowMagnificationController.updateWindowMagnificationInternal(
Float.NaN, Float.NaN, Float.NaN);
});
@@ -1232,7 +1232,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
final int expectedWindowHeight = minimumWindowSize;
final int expectedWindowWidth = minimumWindowSize;
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1259,7 +1259,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
final AtomicInteger actualWindowWidth = new AtomicInteger();
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN, Float.NaN);
actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
@@ -1274,7 +1274,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
final int minimumWindowSize = mResources.getDimensionPixelSize(
com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1294,7 +1294,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
public void setWindowSizeLargerThanScreenSize_enabled_windowSizeIsScreenSize() {
final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1323,7 +1323,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
mInstrumentation.runOnMainSync(
() ->
- mWindowMagnificationController.enableWindowMagnificationInternal(
+ mWindowMagnificationController.updateWindowMagnificationInternal(
Float.NaN, Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1348,7 +1348,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
mInstrumentation.runOnMainSync(
() ->
- mWindowMagnificationController.enableWindowMagnificationInternal(
+ mWindowMagnificationController.updateWindowMagnificationInternal(
Float.NaN, Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1382,7 +1382,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
mInstrumentation.runOnMainSync(
() ->
- mWindowMagnificationController.enableWindowMagnificationInternal(
+ mWindowMagnificationController.updateWindowMagnificationInternal(
Float.NaN, Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1408,7 +1408,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN, Float.NaN));
final AtomicInteger magnificationCenterX = new AtomicInteger();
@@ -1429,7 +1429,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
mInstrumentation.runOnMainSync(
() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(
+ mWindowMagnificationController.updateWindowMagnificationInternal(
1.5f, bounds.centerX(), bounds.centerY());
});
View dragButton = getInternalView(R.id.drag_handle);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
index e9d36b861776..a88654bdbecc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
@@ -282,7 +282,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
@Test
public void enableWindowMagnification_showControlAndNotifyBoundsChanged() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
@@ -351,7 +351,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
@Test
public void enableWindowMagnification_systemGestureExclusionRectsIsSet() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
// Wait for Rects updated.
@@ -368,7 +368,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
@@ -383,7 +383,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
@Test
public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() {
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN,
Float.NaN));
@@ -401,7 +401,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
setSystemGestureInsets();
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
bounds.bottom);
});
ReferenceTestUtils.waitForCondition(this::hasMagnificationOverlapFlag);
@@ -417,7 +417,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
@Test
public void deleteWindowMagnification_notifySourceBoundsChanged() {
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN,
Float.NaN));
@@ -433,7 +433,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
@Test
public void moveMagnifier_schedulesFrame() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
@@ -520,7 +520,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
@Test
public void setScale_enabled_expectedValueAndUpdateStateDescription() {
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(2.0f,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(2.0f,
Float.NaN, Float.NaN));
mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
@@ -553,7 +553,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
final float displayWidth = windowBounds.width();
final PointF magnifiedCenter = new PointF(center, center + 5f);
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
magnifiedCenter.x, magnifiedCenter.y);
// Get the center again in case the center we set is out of screen.
magnifiedCenter.set(mWindowMagnificationController.getCenterX(),
@@ -596,7 +596,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
final float expectedRatio = 0.5f;
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
@@ -635,8 +635,8 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
preferredWindowSize.toString())
.commit();
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController
- .enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN);
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
});
// Screen density and size change
@@ -663,7 +663,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
@Test
public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
@@ -688,7 +688,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
@Test
public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
Mockito.reset(mWindowManager);
Mockito.reset(mMirrorWindowControl);
@@ -717,7 +717,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
@Test
public void initializeA11yNode_enabled_expectedValues() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
Float.NaN);
});
final View mirrorView = mSurfaceControlViewHost.getView();
@@ -741,7 +741,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
public void performA11yActions_visible_expectedResults() {
final int displayId = mContext.getDisplayId();
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(1.5f, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(1.5f, Float.NaN,
Float.NaN);
});
@@ -775,7 +775,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
public void performA11yActions_visible_notifyAccessibilityActionPerformed() {
final int displayId = mContext.getDisplayId();
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
Float.NaN);
});
@@ -788,7 +788,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
@Test
public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
});
@@ -829,7 +829,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
/* pbase= */ 1);
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -871,7 +871,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
/* pbase= */ 1);
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -909,7 +909,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
final int startingHeight = windowBounds.height();
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -929,7 +929,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
final int startingHeight = windowBounds.height();
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -952,7 +952,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
/* base= */ 1,
/* pbase= */ 1);
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingSize, startingSize);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -994,7 +994,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
/* pbase= */ 1);
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingSize, startingSize);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -1032,7 +1032,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
final int startingSize = mMinWindowSize;
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingSize, startingSize);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -1052,7 +1052,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
final int startingSize = mMinWindowSize;
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.setWindowSize(startingSize, startingSize);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
@@ -1068,7 +1068,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
@Test
public void enableWindowMagnification_hasA11yWindowTitle() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
@@ -1079,12 +1079,12 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
@Test
public void enableWindowMagnificationWithScaleLessThanOne_enabled_disabled() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(0.9f, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(0.9f, Float.NaN,
Float.NaN);
});
@@ -1103,7 +1103,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
final int newRotation = simulateRotateTheDevice();
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN, Float.NaN));
assertEquals(newRotation, mWindowMagnificationController.mRotation);
@@ -1112,7 +1112,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
@Test
public void enableWindowMagnification_registerComponentCallback() {
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN,
Float.NaN));
@@ -1123,7 +1123,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
public void onLocaleChanged_enabled_updateA11yWindowTitle() {
final String newA11yWindowTitle = "new a11y window title";
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
final TestableResources testableResources = getContext().getOrCreateTestableResources();
@@ -1141,7 +1141,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
@Test
public void onSingleTap_enabled_scaleAnimates() {
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
@@ -1168,7 +1168,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
setSystemGestureInsets();
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
@@ -1186,7 +1186,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
setSystemGestureInsets();
mInstrumentation.runOnMainSync(
() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(
+ mWindowMagnificationController.updateWindowMagnificationInternal(
Float.NaN, Float.NaN, Float.NaN);
});
// Wait for Region updated.
@@ -1215,7 +1215,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
setSystemGestureInsets();
mInstrumentation.runOnMainSync(
() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(
+ mWindowMagnificationController.updateWindowMagnificationInternal(
Float.NaN, Float.NaN, Float.NaN);
});
// Wait for Region updated.
@@ -1244,7 +1244,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
final int expectedWindowHeight = minimumWindowSize;
final int expectedWindowWidth = minimumWindowSize;
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1271,7 +1271,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
final AtomicInteger actualWindowWidth = new AtomicInteger();
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
- mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN, Float.NaN);
actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
@@ -1286,7 +1286,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
final int minimumWindowSize = mResources.getDimensionPixelSize(
com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1306,7 +1306,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
public void setWindowSizeLargerThanScreenSize_enabled_windowSizeIsScreenSize() {
final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1335,7 +1335,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
mInstrumentation.runOnMainSync(
() ->
- mWindowMagnificationController.enableWindowMagnificationInternal(
+ mWindowMagnificationController.updateWindowMagnificationInternal(
Float.NaN, Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1362,7 +1362,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
mInstrumentation.runOnMainSync(
() ->
- mWindowMagnificationController.enableWindowMagnificationInternal(
+ mWindowMagnificationController.updateWindowMagnificationInternal(
Float.NaN, Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1398,7 +1398,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
mInstrumentation.runOnMainSync(
() ->
- mWindowMagnificationController.enableWindowMagnificationInternal(
+ mWindowMagnificationController.updateWindowMagnificationInternal(
Float.NaN, Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
@@ -1426,7 +1426,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN, Float.NaN));
final AtomicInteger magnificationCenterX = new AtomicInteger();
@@ -1447,7 +1447,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
mInstrumentation.runOnMainSync(
() -> {
- mWindowMagnificationController.enableWindowMagnificationInternal(
+ mWindowMagnificationController.updateWindowMagnificationInternal(
1.5f, bounds.centerX(), bounds.centerY());
});
View dragButton = getInternalView(R.id.drag_handle);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index b0f0363a3e97..15764571ce02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -43,10 +43,10 @@ import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItemType;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItemType;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
index 95d5597ef645..d16db65334d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
@@ -27,7 +27,7 @@ import androidx.test.filters.SmallTest;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
import org.junit.Before;
import org.junit.Rule;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 33a6010d816c..8f3fed74fe97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.biometrics
+import android.app.ActivityTaskManager
import android.app.admin.DevicePolicyManager
import android.content.pm.PackageManager
import android.hardware.biometrics.BiometricAuthenticator
@@ -44,9 +45,12 @@ import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
@@ -120,10 +124,12 @@ open class AuthContainerViewTest : SysuiTestCase() {
lateinit var selectedUserInteractor: SelectedUserInteractor
@Mock
private lateinit var packageManager: PackageManager
+ @Mock private lateinit var activityTaskManager: ActivityTaskManager
private val testScope = TestScope(StandardTestDispatcher())
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private val biometricPromptRepository = FakePromptRepository()
+ private val biometricStatusRepository = FakeBiometricStatusRepository()
private val fingerprintRepository = FakeFingerprintPropertyRepository()
private val displayStateRepository = FakeDisplayStateRepository()
private val credentialInteractor = FakeCredentialInteractor()
@@ -143,6 +149,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
private lateinit var displayRepository: FakeDisplayRepository
private lateinit var displayStateInteractor: DisplayStateInteractor
private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
+ private lateinit var biometricStatusInteractor: BiometricStatusInteractor
private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
@@ -168,6 +175,8 @@ open class AuthContainerViewTest : SysuiTestCase() {
selectedUserInteractor,
testScope.backgroundScope,
)
+ biometricStatusInteractor =
+ BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
// Set up default logo icon
whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
context.setMockPackageManager(packageManager)
@@ -645,6 +654,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
promptSelectorInteractor,
context,
udfpsOverlayInteractor,
+ biometricStatusInteractor,
udfpsUtils
),
{ credentialViewModel },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
index bf6caad688e2..31bdde2bc895 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
@@ -1,13 +1,11 @@
package com.android.systemui.biometrics.domain.interactor
-import android.view.Display
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.display.data.repository.FakeDisplayRepository
-import com.android.systemui.display.data.repository.display
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.util.concurrency.FakeExecutor
@@ -101,14 +99,11 @@ class DisplayStateInteractorImplTest : SysuiTestCase() {
fun isDefaultDisplayOffChanges() =
testScope.runTest {
val isDefaultDisplayOff by collectLastValue(interactor.isDefaultDisplayOff)
- runCurrent()
- displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_OFF)))
- displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+ displayRepository.setDefaultDisplayOff(true)
assertThat(isDefaultDisplayOff).isTrue()
- displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_ON)))
- displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+ displayRepository.setDefaultDisplayOff(false)
assertThat(isDefaultDisplayOff).isFalse()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
index 99c2c4076403..aff93bd339ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
@@ -465,10 +465,34 @@ private val ROTATION_90_INPUTS =
nativeYOutsideSensor = 150f,
)
-/* ROTATION_180 is not supported. It's treated the same as ROTATION_0. */
+/*
+ * ROTATION_180 map:
+ * _ _ _ _
+ * _ _ s _
+ * _ _ s _
+ * _ _ _ _
+ * _ O _ _
+ * _ _ _ _
+ *
+ * (_) empty space
+ * (S) sensor
+ * (O) touch outside of the sensor
+ */
+private val ROTATION_180_NATIVE_SENSOR_BOUNDS =
+ Rect(
+ 200, /* left */
+ 100, /* top */
+ 300, /* right */
+ 300, /* bottom */
+ )
private val ROTATION_180_INPUTS =
- ROTATION_0_INPUTS.copy(
+ OrientationBasedInputs(
rotation = Surface.ROTATION_180,
+ nativeOrientation = (ORIENTATION - Math.PI.toFloat() / 2),
+ nativeXWithinSensor = ROTATION_180_NATIVE_SENSOR_BOUNDS.exactCenterX(),
+ nativeYWithinSensor = ROTATION_180_NATIVE_SENSOR_BOUNDS.exactCenterY(),
+ nativeXOutsideSensor = 150f,
+ nativeYOutsideSensor = 450f,
)
/*
@@ -639,33 +663,6 @@ private fun genPositiveTestCases(
}
}
-private fun genTestCasesForUnsupportedAction(
- motionEventAction: Int
-): List<SinglePointerTouchProcessorTest.TestCase> {
- val isGoodOverlap = true
- val previousPointerOnSensorIds = listOf(INVALID_POINTER_ID, POINTER_ID_1)
- return previousPointerOnSensorIds.map { previousPointerOnSensorId ->
- val overlayParams = ROTATION_0_INPUTS.toOverlayParams(scaleFactor = 1f)
- val nativeX = ROTATION_0_INPUTS.getNativeX(isGoodOverlap)
- val nativeY = ROTATION_0_INPUTS.getNativeY(isGoodOverlap)
- val event =
- MOTION_EVENT.copy(
- action = motionEventAction,
- x = nativeX,
- y = nativeY,
- minor = NATIVE_MINOR,
- major = NATIVE_MAJOR,
- )
- SinglePointerTouchProcessorTest.TestCase(
- event = event,
- currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = isGoodOverlap)),
- previousPointerOnSensorId = previousPointerOnSensorId,
- overlayParams = overlayParams,
- expected = TouchProcessorResult.Failure(),
- )
- }
-}
-
private fun obtainMotionEvent(
action: Int,
pointerId: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 5b0df5d05703..a6c7f728ff3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,12 +16,14 @@
package com.android.systemui.biometrics.ui.viewmodel
+import android.app.ActivityTaskManager
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.drawable.BitmapDrawable
+import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
import android.hardware.biometrics.PromptContentItemBulletedText
import android.hardware.biometrics.PromptContentView
@@ -40,9 +42,12 @@ import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.UdfpsUtils
+import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
@@ -51,6 +56,7 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.extractAuthenticatorTypes
import com.android.systemui.biometrics.faceSensorPropertiesInternal
import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.DisplayRotation
@@ -59,6 +65,7 @@ import com.android.systemui.biometrics.shared.model.toSensorType
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
@@ -102,6 +109,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var applicationInfoWithIcon: ApplicationInfo
@Mock private lateinit var applicationInfoNoIcon: ApplicationInfo
+ @Mock private lateinit var activityTaskManager: ActivityTaskManager
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private val testScope = TestScope()
@@ -115,9 +123,11 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
private lateinit var promptRepository: FakePromptRepository
private lateinit var displayStateRepository: FakeDisplayStateRepository
+ private lateinit var biometricStatusRepository: FakeBiometricStatusRepository
private lateinit var displayRepository: FakeDisplayRepository
private lateinit var displayStateInteractor: DisplayStateInteractor
private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
+ private lateinit var biometricStatusInteractor: BiometricStatusInteractor
private lateinit var selector: PromptSelectorInteractor
private lateinit var viewModel: PromptViewModel
@@ -157,6 +167,9 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
selectedUserInteractor,
testScope.backgroundScope
)
+ biometricStatusRepository = FakeBiometricStatusRepository()
+ biometricStatusInteractor =
+ BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
selector =
PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
selector.resetPrompt()
@@ -178,6 +191,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
selector,
mContext,
udfpsOverlayInteractor,
+ biometricStatusInteractor,
udfpsUtils
)
iconViewModel = viewModel.iconViewModel
@@ -1053,8 +1067,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
fun auto_confirm_authentication_when_finger_down() = runGenericTest {
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
- // No icon button when face only, can't confirm before auth
- if (!testCase.isFaceOnly) {
+ if (testCase.isCoex) {
viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
}
viewModel.showAuthenticated(testCase.authenticatedModality, 0)
@@ -1069,14 +1082,18 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
assertThat(canTryAgain).isFalse()
assertThat(authenticated?.isAuthenticated).isTrue()
- if (testCase.isFaceOnly && expectConfirmation) {
- assertThat(size).isEqualTo(PromptSize.MEDIUM)
- assertButtonsVisible(
- cancel = true,
- confirm = true,
- )
+ if (expectConfirmation) {
+ if (testCase.isFaceOnly) {
+ assertThat(size).isEqualTo(PromptSize.MEDIUM)
+ assertButtonsVisible(
+ cancel = true,
+ confirm = true,
+ )
- viewModel.confirmAuthenticated()
+ viewModel.confirmAuthenticated()
+ } else if (testCase.isCoex) {
+ assertThat(authenticated?.isAuthenticatedAndConfirmed).isTrue()
+ }
assertThat(message).isEqualTo(PromptMessage.Empty)
assertButtonsVisible()
}
@@ -1086,8 +1103,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
fun cannot_auto_confirm_authentication_when_finger_up() = runGenericTest {
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
- // No icon button when face only, can't confirm before auth
- if (!testCase.isFaceOnly) {
+ if (testCase.isCoex) {
viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_UP))
}
@@ -1413,6 +1429,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
packageName = packageName,
)
+ biometricStatusRepository.setFingerprintAcquiredStatus(
+ AcquiredFingerprintAuthenticationStatus(
+ AuthenticationReason.BiometricPromptAuthentication,
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_UNKNOWN
+ )
+ )
// put the view model in the initial authenticating state, unless explicitly skipped
val startMode =
when {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
index 036d3c862ae0..4949716ad129 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt
index 31192841ec77..85e2a8d4b48e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
index 479e62d4d724..a8f82eda51c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
@@ -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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 17b612714fe2..12dfe97649d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -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,9 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
-import android.content.Context
import android.graphics.drawable.Drawable
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -121,23 +120,18 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
sysuiDialogFactory
)
- whenever(
- sysuiDialogFactory.create(
- any(SystemUIDialog.Delegate::class.java)
- )
+ whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java))).thenAnswer {
+ SystemUIDialog(
+ mContext,
+ 0,
+ SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ dialogManager,
+ sysuiState,
+ fakeBroadcastDispatcher,
+ dialogTransitionAnimator,
+ it.getArgument(0)
)
- .thenAnswer {
- SystemUIDialog(
- mContext,
- 0,
- SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
- dialogManager,
- sysuiState,
- fakeBroadcastDispatcher,
- dialogTransitionAnimator,
- it.getArgument(0)
- )
- }
+ }
icon = Pair(drawable, DEVICE_NAME)
deviceItem =
@@ -163,6 +157,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
assertThat(recyclerView.visibility).isEqualTo(VISIBLE)
assertThat(recyclerView.adapter).isNotNull()
assertThat(recyclerView.layoutManager is LinearLayoutManager).isTrue()
+ dialog.dismiss()
}
@Test
@@ -184,6 +179,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME)
assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY)
assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon)
+ dialog.dismiss()
}
}
@@ -259,6 +255,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
assertThat(pairNewButton.visibility).isEqualTo(VISIBLE)
assertThat(adapter.itemCount).isEqualTo(1)
assertThat(scrollViewContent.layoutParams.height).isEqualTo(WRAP_CONTENT)
+ dialog.dismiss()
}
}
@@ -283,6 +280,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
dialog.show()
assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height)
.isEqualTo(cachedHeight)
+ dialog.dismiss()
}
}
@@ -306,6 +304,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
dialog.show()
assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height)
.isGreaterThan(MATCH_PARENT)
+ dialog.dismiss()
}
}
@@ -331,6 +330,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
dialog.requireViewById<View>(R.id.bluetooth_auto_on_toggle_layout).visibility
)
.isEqualTo(GONE)
+ dialog.dismiss()
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt
index da8f60a9b926..4aa6209fab3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt
@@ -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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index c8a2aa64ffa2..6d99c5b62e9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index a8cd8c801a95..28cbcb435223 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothDevice
import android.content.pm.PackageInfo
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
index ddf0b9a78165..eb735cbfec47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
@@ -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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 45d20dcd9d11..8e5ddc79976f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -202,6 +203,36 @@ public class FalsingCollectorImplTest extends SysuiTestCase {
}
@Test
+ public void testPassThroughEnterKeyEvent() {
+ KeyEvent enterDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER,
+ 0, 0, 0, 0, 0, 0, 0, "");
+ KeyEvent enterUp = KeyEvent.obtain(0, 0, MotionEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0,
+ 0, 0, 0, 0, 0, 0, "");
+
+ mFalsingCollector.onKeyEvent(enterDown);
+ verify(mFalsingDataProvider, never()).onKeyEvent(any(KeyEvent.class));
+
+ mFalsingCollector.onKeyEvent(enterUp);
+ verify(mFalsingDataProvider, times(1)).onKeyEvent(enterUp);
+ }
+
+ @Test
+ public void testAvoidAKeyEvent() {
+ // Arbitrarily chose the "A" key, as it is not currently allowlisted. If this key is
+ // allowlisted in the future, please choose another key that will not be collected.
+ KeyEvent aKeyDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A,
+ 0, 0, 0, 0, 0, 0, 0, "");
+ KeyEvent aKeyUp = KeyEvent.obtain(0, 0, MotionEvent.ACTION_UP, KeyEvent.KEYCODE_A, 0,
+ 0, 0, 0, 0, 0, 0, "");
+
+ mFalsingCollector.onKeyEvent(aKeyDown);
+ verify(mFalsingDataProvider, never()).onKeyEvent(any(KeyEvent.class));
+
+ mFalsingCollector.onKeyEvent(aKeyUp);
+ verify(mFalsingDataProvider, never()).onKeyEvent(any(KeyEvent.class));
+ }
+
+ @Test
public void testPassThroughGesture() {
MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 0353f87ae9b4..057b0a158cab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.when;
import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -282,6 +283,22 @@ public class FalsingDataProviderTest extends ClassifierTest {
}
@Test
+ public void test_isFromKeyboard_disallowedKey_false() {
+ KeyEvent eventDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0,
+ 0, 0, 0, 0, 0, 0, "");
+ KeyEvent eventUp = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0, 0,
+ 0, 0, 0, 0, 0, "");
+
+ //events have not come in yet
+ assertThat(mDataProvider.isFromKeyboard()).isFalse();
+
+ mDataProvider.onKeyEvent(eventDown);
+ mDataProvider.onKeyEvent(eventUp);
+ assertThat(mDataProvider.isFromKeyboard()).isTrue();
+ mDataProvider.onSessionEnd();
+ }
+
+ @Test
public void test_IsFromTrackpad() {
MotionEvent motionEventOrigin = appendTrackpadDownEvent(0, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedMotionEventBufferTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
index 901196f8bbba..ad7afa3c593f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedMotionEventBufferTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
@@ -20,6 +20,8 @@ import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import android.testing.AndroidTestingRunner;
+import android.view.InputEvent;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -34,28 +36,28 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
-public class TimeLimitedMotionEventBufferTest extends SysuiTestCase {
+public class TimeLimitedInputEventBufferTest extends SysuiTestCase {
private static final long MAX_AGE_MS = 100;
- private TimeLimitedMotionEventBuffer mBuffer;
+ private TimeLimitedInputEventBuffer<InputEvent> mBuffer;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mBuffer = new TimeLimitedMotionEventBuffer(MAX_AGE_MS);
+ mBuffer = new TimeLimitedInputEventBuffer<>(MAX_AGE_MS);
}
@After
public void tearDown() {
- for (MotionEvent motionEvent : mBuffer) {
- motionEvent.recycle();
+ for (InputEvent inputEvent : mBuffer) {
+ inputEvent.recycle();
}
mBuffer.clear();
}
@Test
- public void testAllEventsRetained() {
+ public void testMotionEventAllEventsRetained() {
MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
MotionEvent eventC = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 0, 0, 0);
@@ -70,7 +72,7 @@ public class TimeLimitedMotionEventBufferTest extends SysuiTestCase {
}
@Test
- public void testOlderEventsRemoved() {
+ public void testMotionEventOlderEventsRemoved() {
MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
MotionEvent eventC = MotionEvent.obtain(
@@ -89,4 +91,40 @@ public class TimeLimitedMotionEventBufferTest extends SysuiTestCase {
assertThat(mBuffer.get(0), is(eventC));
assertThat(mBuffer.get(1), is(eventD));
}
+
+ @Test
+ public void testKeyEventAllEventsRetained() {
+ KeyEvent eventA = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0, 0,
+ 0, 0, 0, 0, 0, "");
+ KeyEvent eventB = KeyEvent.obtain(0, 3, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A, 0, 0, 0, 0,
+ 0, 0, 0, "");
+
+ mBuffer.add(eventA);
+ mBuffer.add(eventB);
+
+ assertThat(mBuffer.size(), is(2));
+ }
+
+ @Test
+ public void testKeyEventOlderEventsRemoved() {
+ KeyEvent eventA = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0, 0,
+ 0, 0, 0, 0, 0, "");
+ KeyEvent eventB = KeyEvent.obtain(0, 1, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A, 0, 0, 0, 0,
+ 0, 0, 0, "");
+ KeyEvent eventC = KeyEvent.obtain(0, MAX_AGE_MS + 1, MotionEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_A, 0, 0, 0, 0, 0, 0, 0, "");
+ KeyEvent eventD = KeyEvent.obtain(0, MAX_AGE_MS + 2, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A,
+ 0, 0, 0, 0, 0, 0, 0, "");
+
+ mBuffer.add(eventA);
+ mBuffer.add(eventB);
+ assertThat(mBuffer.size(), is(2));
+
+ mBuffer.add(eventC);
+ mBuffer.add(eventD);
+ assertThat(mBuffer.size(), is(2));
+
+ assertThat(mBuffer.get(0), is(eventC));
+ assertThat(mBuffer.get(1), is(eventD));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 806930d091b1..68d49c78c567 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -22,6 +22,7 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Display
import android.view.Display.TYPE_EXTERNAL
+import android.view.Display.TYPE_INTERNAL
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.FlowValue
@@ -427,6 +428,35 @@ class DisplayRepositoryTest : SysuiTestCase() {
assertThat(display!!.type).isEqualTo(TYPE_EXTERNAL)
}
+ @Test
+ fun defaultDisplayOff_changes() =
+ testScope.runTest {
+ val defaultDisplayOff by latestDefaultDisplayOffFlowValue()
+ setDisplays(
+ listOf(
+ display(
+ type = TYPE_INTERNAL,
+ id = Display.DEFAULT_DISPLAY,
+ state = Display.STATE_OFF
+ )
+ )
+ )
+ displayListener.value.onDisplayChanged(Display.DEFAULT_DISPLAY)
+ assertThat(defaultDisplayOff).isTrue()
+
+ setDisplays(
+ listOf(
+ display(
+ type = TYPE_INTERNAL,
+ id = Display.DEFAULT_DISPLAY,
+ state = Display.STATE_ON
+ )
+ )
+ )
+ displayListener.value.onDisplayChanged(Display.DEFAULT_DISPLAY)
+ assertThat(defaultDisplayOff).isFalse()
+ }
+
private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
// Wrapper to capture the displayListener.
@@ -436,6 +466,13 @@ class DisplayRepositoryTest : SysuiTestCase() {
return flowValue
}
+ // Wrapper to capture the displayListener.
+ private fun TestScope.latestDefaultDisplayOffFlowValue(): FlowValue<Boolean?> {
+ val flowValue = collectLastValue(displayRepository.defaultDisplayOff)
+ captureAddedRemovedListener()
+ return flowValue
+ }
+
private fun TestScope.lastPendingDisplay(): FlowValue<DisplayRepository.PendingDisplay?> {
val flowValue = collectLastValue(displayRepository.pendingDisplay)
captureAddedRemovedListener()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 11ec417fc5f8..318227f2d1a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -91,6 +91,7 @@ import com.android.systemui.biometrics.AuthController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
import com.android.systemui.dump.DumpManager;
@@ -219,6 +220,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock CoroutineDispatcher mDispatcher;
private @Mock DreamViewModel mDreamViewModel;
+ private @Mock CommunalTransitionViewModel mCommunalTransitionViewModel;
private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
private @Mock SceneContainerFlags mSceneContainerFlags;
@@ -241,6 +243,10 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
.thenReturn(mock(Flow.class));
when(mDreamViewModel.getTransitionEnded())
.thenReturn(mock(Flow.class));
+ when(mCommunalTransitionViewModel.getShowByDefault())
+ .thenReturn(mock(Flow.class));
+ when(mCommunalTransitionViewModel.getTransitionFromOccludedEnded())
+ .thenReturn(mock(Flow.class));
when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId);
when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId);
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
@@ -1229,6 +1235,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mSystemClock,
mDispatcher,
() -> mDreamViewModel,
+ () -> mCommunalTransitionViewModel,
mSystemPropertiesHelper,
() -> mock(WindowManagerLockscreenVisibilityManager.class),
mSelectedUserInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
index d75cbec8c542..d52e911d31f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
@@ -21,7 +21,10 @@ import androidx.test.filters.SmallTest
import com.android.keyguard.ClockEventController
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.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.res.R
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth
@@ -49,6 +52,7 @@ class KeyguardClockRepositoryTest : SysuiTestCase() {
private lateinit var fakeSettings: FakeSettings
@Mock private lateinit var clockRegistry: ClockRegistry
@Mock private lateinit var clockEventController: ClockEventController
+ private val fakeFeatureFlagsClassic = FakeFeatureFlagsClassic()
@Before
fun setup() {
@@ -63,7 +67,9 @@ class KeyguardClockRepositoryTest : SysuiTestCase() {
clockRegistry,
clockEventController,
dispatcher,
- scope.backgroundScope
+ scope.backgroundScope,
+ context,
+ fakeFeatureFlagsClassic,
)
}
@@ -82,4 +88,12 @@ class KeyguardClockRepositoryTest : SysuiTestCase() {
val value = collectLastValue(underTest.selectedClockSize)
Truth.assertThat(value()).isEqualTo(SettingsClockSize.DYNAMIC)
}
+
+ @Test
+ fun testShouldForceSmallClock() =
+ scope.runTest {
+ overrideResource(R.bool.force_small_clock_on_lockscreen, true)
+ fakeFeatureFlagsClassic.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, true)
+ Truth.assertThat(underTest.shouldForceSmallClock).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index b6b457142a3f..4270236f761e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -27,16 +27,12 @@ import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRe
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
-import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.clocks.ClockConfig
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -103,72 +99,6 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
- fun composeLockscreenOff_DoesAppliesSplitShadeWeatherClockBlueprint() {
- testScope.runTest {
- val blueprint by collectLastValue(underTest.blueprint)
- whenever(clockController.config)
- .thenReturn(
- ClockConfig(
- id = "DIGITAL_CLOCK_WEATHER",
- name = "clock",
- description = "clock",
- )
- )
- clockRepository.setCurrentClock(clockController)
- overrideResource(R.bool.config_use_split_notification_shade, true)
- configurationRepository.onConfigurationChange()
- runCurrent()
-
- assertThat(blueprint?.id).isNotEqualTo(SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID)
- }
- }
-
- @Test
- @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
- fun testDoesAppliesSplitShadeWeatherClockBlueprint() {
- testScope.runTest {
- val blueprint by collectLastValue(underTest.blueprint)
- whenever(clockController.config)
- .thenReturn(
- ClockConfig(
- id = "DIGITAL_CLOCK_WEATHER",
- name = "clock",
- description = "clock",
- )
- )
- clockRepository.setCurrentClock(clockController)
- overrideResource(R.bool.config_use_split_notification_shade, true)
- configurationRepository.onConfigurationChange()
- runCurrent()
-
- assertThat(blueprint?.id).isEqualTo(SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID)
- }
- }
-
- @Test
- @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
- fun testAppliesWeatherClockBlueprint() {
- testScope.runTest {
- val blueprint by collectLastValue(underTest.blueprint)
- whenever(clockController.config)
- .thenReturn(
- ClockConfig(
- id = "DIGITAL_CLOCK_WEATHER",
- name = "clock",
- description = "clock",
- )
- )
- clockRepository.setCurrentClock(clockController)
- overrideResource(R.bool.config_use_split_notification_shade, false)
- configurationRepository.onConfigurationChange()
- runCurrent()
-
- assertThat(blueprint?.id).isEqualTo(WEATHER_CLOCK_BLUEPRINT_ID)
- }
- }
-
- @Test
@EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
fun testDoesNotApplySplitShadeBlueprint() {
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 2c0a518350da..e155a1b52595 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -27,6 +27,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeCommandQueue
@@ -108,6 +110,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
private val powerInteractor = kosmos.powerInteractor
private val communalInteractor = kosmos.communalInteractor
+ private val dockManager = kosmos.fakeDockManager
@Before
fun setUp() {
@@ -1230,6 +1233,38 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
}
@Test
+ fun occludedToGlanceableHubWhenDocked() =
+ testScope.runTest {
+ // GIVEN a device on lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to OCCLUDED
+ runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ // GIVEN device is docked
+ dockManager.setIsDocked(true)
+ dockManager.setDockEvent(DockManager.STATE_DOCKED)
+
+ // WHEN occlusion ends
+ keyguardRepository.setKeyguardOccluded(false)
+ runCurrent()
+
+ // THEN a transition to GLANCEABLE_HUB should occur
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromOccludedTransitionInteractor::class.simpleName,
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNotNull() },
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun occludedToAlternateBouncer() =
testScope.runTest {
// GIVEN a prior transition has run to OCCLUDED
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index b5f668cef08c..4f2b690f9fcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -38,7 +38,6 @@ import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
@@ -86,7 +85,6 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() {
{ mock(DeviceEntryBackgroundViewModel::class.java) },
{ falsingManager },
{ mock(VibratorHelper::class.java) },
- mock(CoroutineDispatcher::class.java),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 7b5dd1fc6c7a..01754c4b5598 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -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.
@@ -12,191 +12,235 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
*/
package com.android.systemui.keyguard.ui.viewmodel
-import android.provider.Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.filters.SmallTest
-import com.android.keyguard.ClockEventController
-import com.android.keyguard.KeyguardClockSwitch.LARGE
-import com.android.keyguard.KeyguardClockSwitch.SMALL
+import com.android.keyguard.KeyguardClockSwitch
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
-import com.android.systemui.keyguard.data.repository.KeyguardClockRepositoryImpl
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.shared.ComposeLockscreen
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.keyguardClockRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.res.R
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.shared.clocks.ClockRegistry
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.util.Utils
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.mock
@SmallTest
@RunWith(JUnit4::class)
+@DisableSceneContainer
class KeyguardClockViewModelTest : SysuiTestCase() {
- private lateinit var scheduler: TestCoroutineScheduler
- private lateinit var dispatcher: CoroutineDispatcher
- private lateinit var scope: TestScope
+ private lateinit var kosmos: Kosmos
private lateinit var underTest: KeyguardClockViewModel
- private lateinit var keyguardInteractor: KeyguardInteractor
- private lateinit var keyguardRepository: KeyguardRepository
- private lateinit var keyguardClockInteractor: KeyguardClockInteractor
- private lateinit var keyguardClockRepository: KeyguardClockRepository
- private lateinit var fakeSettings: FakeSettings
- private val shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
- @Mock private lateinit var clockRegistry: ClockRegistry
- @Mock private lateinit var clock: ClockController
- @Mock private lateinit var largeClock: ClockFaceController
- @Mock private lateinit var clockFaceConfig: ClockFaceConfig
- @Mock private lateinit var eventController: ClockEventController
- @Mock private lateinit var notifsKeyguardInteractor: NotificationsKeyguardInteractor
- @Mock private lateinit var areNotificationsFullyHidden: Flow<Boolean>
- @Mock private lateinit var shadeInteractor: ShadeInteractor
+ private lateinit var testScope: TestScope
+ private lateinit var clockController: ClockController
+ private lateinit var config: ClockFaceConfig
@Before
fun setup() {
- MockitoAnnotations.initMocks(this)
- KeyguardInteractorFactory.create().let {
- keyguardInteractor = it.keyguardInteractor
- keyguardRepository = it.repository
- }
- fakeSettings = FakeSettings()
- scheduler = TestCoroutineScheduler()
- dispatcher = StandardTestDispatcher(scheduler)
- scope = TestScope(dispatcher)
- setupMockClock()
- keyguardClockRepository =
- KeyguardClockRepositoryImpl(
- fakeSettings,
- clockRegistry,
- eventController,
- dispatcher,
- scope.backgroundScope
- )
- keyguardClockInteractor = KeyguardClockInteractor(keyguardClockRepository)
- whenever(notifsKeyguardInteractor.areNotificationsFullyHidden)
- .thenReturn(areNotificationsFullyHidden)
- whenever(shadeInteractor.shadeMode).thenReturn(shadeMode)
- underTest =
- KeyguardClockViewModel(
- keyguardInteractor,
- keyguardClockInteractor,
- scope.backgroundScope,
- notifsKeyguardInteractor,
- shadeInteractor,
- )
+ kosmos = testKosmos()
+ testScope = kosmos.testScope
+ underTest = kosmos.keyguardClockViewModel
+
+ clockController = mock(ClockController::class.java)
+ val largeClock = mock(ClockFaceController::class.java)
+ config = mock(ClockFaceConfig::class.java)
+
+ whenever(clockController.largeClock).thenReturn(largeClock)
+ whenever(largeClock.config).thenReturn(config)
}
@Test
- fun testClockSize_alwaysSmallClock() =
- scope.runTest {
- // When use double line clock is disabled,
- // should always return small
- fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 0)
- keyguardClockRepository.setClockSize(LARGE)
- val value = collectLastValue(underTest.clockSize)
- assertThat(value()).isEqualTo(SMALL)
+ fun currentClockLayout_splitShadeOn_clockCentered_largeClock() =
+ testScope.runTest {
+ with(kosmos) {
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ keyguardRepository.setClockShouldBeCentered(true)
+ keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+ }
+ val currentClockLayout by collectLastValue(underTest.currentClockLayout)
+ assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.LARGE_CLOCK)
+ }
+
+ @Test
+ fun currentClockLayout_splitShadeOn_clockNotCentered_largeClock_splitShadeLargeClock() =
+ testScope.runTest {
+ with(kosmos) {
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ keyguardRepository.setClockShouldBeCentered(false)
+ keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+ }
+ val currentClockLayout by collectLastValue(underTest.currentClockLayout)
+ assertThat(currentClockLayout)
+ .isEqualTo(KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK)
+ }
+
+ @Test
+ fun currentClockLayout_splitShadeOn_clockNotCentered_smallClock_splitShadeSmallClock() =
+ testScope.runTest {
+ with(kosmos) {
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ keyguardRepository.setClockShouldBeCentered(false)
+ keyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
+ }
+ val currentClockLayout by collectLastValue(underTest.currentClockLayout)
+ assertThat(currentClockLayout)
+ .isEqualTo(KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_SMALL_CLOCK)
+ }
+
+ @Test
+ fun currentClockLayout_singleShade_smallClock_smallClock() =
+ testScope.runTest {
+ with(kosmos) {
+ shadeRepository.setShadeMode(ShadeMode.Single)
+ keyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
+ }
+ val currentClockLayout by collectLastValue(underTest.currentClockLayout)
+ assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.SMALL_CLOCK)
+ }
+
+ @Test
+ fun currentClockLayout_singleShade_largeClock_largeClock() =
+ testScope.runTest {
+ with(kosmos) {
+ shadeRepository.setShadeMode(ShadeMode.Single)
+ keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+ }
+ val currentClockLayout by collectLastValue(underTest.currentClockLayout)
+ assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.LARGE_CLOCK)
+ }
+
+ @Test
+ fun hasCustomPositionUpdatedAnimation_withConfigTrue_isTrue() =
+ testScope.runTest {
+ with(kosmos) {
+ keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+ whenever(config.hasCustomPositionUpdatedAnimation).thenReturn(true)
+ fakeKeyguardClockRepository.setCurrentClock(clockController)
+ }
+
+ val hasCustomPositionUpdatedAnimation by
+ collectLastValue(underTest.hasCustomPositionUpdatedAnimation)
+ assertThat(hasCustomPositionUpdatedAnimation).isEqualTo(true)
+ }
+
+ @Test
+ fun hasCustomPositionUpdatedAnimation_withConfigFalse_isFalse() =
+ testScope.runTest {
+ with(kosmos) {
+ keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+
+ whenever(config.hasCustomPositionUpdatedAnimation).thenReturn(false)
+ fakeKeyguardClockRepository.setCurrentClock(clockController)
+ }
+
+ val hasCustomPositionUpdatedAnimation by
+ collectLastValue(underTest.hasCustomPositionUpdatedAnimation)
+ assertThat(hasCustomPositionUpdatedAnimation).isEqualTo(false)
+ }
+
+ @Test
+ fun testClockSize_alwaysSmallClockSize() =
+ testScope.runTest {
+ kosmos.fakeKeyguardClockRepository.setSelectedClockSize(SettingsClockSize.SMALL)
+ kosmos.keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+
+ val value by collectLastValue(underTest.clockSize)
+ assertThat(value).isEqualTo(KeyguardClockSwitch.SMALL)
}
@Test
fun testClockSize_dynamicClockSize() =
- scope.runTest {
- fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1)
- keyguardClockRepository.setClockSize(SMALL)
- var value = collectLastValue(underTest.clockSize)
- assertThat(value()).isEqualTo(SMALL)
-
- keyguardClockRepository.setClockSize(LARGE)
- value = collectLastValue(underTest.clockSize)
- assertThat(value()).isEqualTo(LARGE)
+ testScope.runTest {
+ kosmos.keyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
+ kosmos.fakeKeyguardClockRepository.setSelectedClockSize(SettingsClockSize.DYNAMIC)
+ val value by collectLastValue(underTest.clockSize)
+ assertThat(value).isEqualTo(KeyguardClockSwitch.SMALL)
+
+ kosmos.keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+ assertThat(value).isEqualTo(KeyguardClockSwitch.LARGE)
}
@Test
fun isLargeClockVisible_whenLargeClockSize_isTrue() =
- scope.runTest {
- fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1)
- keyguardClockRepository.setClockSize(LARGE)
- var value = collectLastValue(underTest.isLargeClockVisible)
- assertThat(value()).isEqualTo(true)
+ testScope.runTest {
+ kosmos.keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+ val value by collectLastValue(underTest.isLargeClockVisible)
+ assertThat(value).isEqualTo(true)
}
@Test
fun isLargeClockVisible_whenSmallClockSize_isFalse() =
- scope.runTest {
- fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1)
- keyguardClockRepository.setClockSize(SMALL)
- var value = collectLastValue(underTest.isLargeClockVisible)
- assertThat(value()).isEqualTo(false)
+ testScope.runTest {
+ kosmos.keyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
+ val value by collectLastValue(underTest.isLargeClockVisible)
+ assertThat(value).isEqualTo(false)
}
@Test
- fun testSmallClockTop_splitshade() =
- scope.runTest {
- shadeMode.value = ShadeMode.Split
- if (!ComposeLockscreen.isEnabled) {
- assertThat(underTest.getSmallClockTopMargin(context))
- .isEqualTo(
- context.resources.getDimensionPixelSize(
- R.dimen.keyguard_split_shade_top_margin
- )
- )
- } else {
- assertThat(underTest.getSmallClockTopMargin(context))
- .isEqualTo(
- context.resources.getDimensionPixelSize(
- R.dimen.keyguard_split_shade_top_margin
- ) - Utils.getStatusBarHeaderHeightKeyguard(context)
- )
- }
+ @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ fun testSmallClockTop_splitShade_composeLockscreenOn() =
+ testScope.runTest {
+ kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ assertThat(underTest.getSmallClockTopMargin(context))
+ .isEqualTo(
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyguard_split_shade_top_margin
+ ) - Utils.getStatusBarHeaderHeightKeyguard(context)
+ )
}
@Test
- fun testSmallClockTop_nonSplitshade() =
- scope.runTest {
- if (!ComposeLockscreen.isEnabled) {
- assertThat(underTest.getSmallClockTopMargin(context))
- .isEqualTo(
- context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
- Utils.getStatusBarHeaderHeightKeyguard(context)
- )
- } else {
- assertThat(underTest.getSmallClockTopMargin(context))
- .isEqualTo(
- context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
- )
- }
+ @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ fun testSmallClockTop_splitShade_composeLockscreenOff() =
+ testScope.runTest {
+ kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ assertThat(underTest.getSmallClockTopMargin(context))
+ .isEqualTo(
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
+ )
}
- private fun setupMockClock() {
- whenever(clock.largeClock).thenReturn(largeClock)
- whenever(largeClock.config).thenReturn(clockFaceConfig)
- whenever(clockFaceConfig.hasCustomWeatherDataDisplay).thenReturn(false)
- whenever(clockRegistry.createCurrentClock()).thenReturn(clock)
- }
+ @Test
+ @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ fun testSmallClockTop_nonSplitShade_composeLockscreenOn() =
+ testScope.runTest {
+ assertThat(underTest.getSmallClockTopMargin(context))
+ .isEqualTo(
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ fun testSmallClockTop_nonSplitShade_composeLockscreenOff() =
+ testScope.runTest {
+ assertThat(underTest.getSmallClockTopMargin(context))
+ .isEqualTo(
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
+ Utils.getStatusBarHeaderHeightKeyguard(context)
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt
deleted file mode 100644
index d12980a74a18..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt
+++ /dev/null
@@ -1,152 +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.keyguard.ui.viewmodel
-
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardClockSwitch
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
-import com.android.systemui.keyguard.data.repository.keyguardClockRepository
-import com.android.systemui.keyguard.data.repository.keyguardRepository
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.plugins.clocks.ClockFaceConfig
-import com.android.systemui.plugins.clocks.ClockFaceController
-import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.Test
-import kotlinx.coroutines.test.runTest
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.mock
-
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardClockViewModelWithKosmosTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val underTest = kosmos.keyguardClockViewModel
- private val testScope = kosmos.testScope
-
- @Test
- fun currentClockLayout_splitShadeOn_clockCentered_largeClock() =
- testScope.runTest {
- with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Split)
- keyguardRepository.setClockShouldBeCentered(true)
- keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
- }
- val currentClockLayout by collectLastValue(underTest.currentClockLayout)
- assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.LARGE_CLOCK)
- }
-
- @Test
- fun currentClockLayout_splitShadeOn_clockNotCentered_largeClock_splitShadeLargeClock() =
- testScope.runTest {
- with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Split)
- keyguardRepository.setClockShouldBeCentered(false)
- keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
- }
- val currentClockLayout by collectLastValue(underTest.currentClockLayout)
- assertThat(currentClockLayout)
- .isEqualTo(KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK)
- }
-
- @Test
- fun currentClockLayout_splitShadeOn_clockNotCentered_smallClock_splitShadeSmallClock() =
- testScope.runTest {
- with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Split)
- keyguardRepository.setClockShouldBeCentered(false)
- keyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
- }
- val currentClockLayout by collectLastValue(underTest.currentClockLayout)
- assertThat(currentClockLayout)
- .isEqualTo(KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_SMALL_CLOCK)
- }
-
- @Test
- fun currentClockLayout_singleShade_smallClock_smallClock() =
- testScope.runTest {
- with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Single)
- keyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
- }
- val currentClockLayout by collectLastValue(underTest.currentClockLayout)
- assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.SMALL_CLOCK)
- }
-
- @Test
- fun currentClockLayout_singleShade_largeClock_largeClock() =
- testScope.runTest {
- with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Single)
- keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
- }
- val currentClockLayout by collectLastValue(underTest.currentClockLayout)
- assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.LARGE_CLOCK)
- }
-
- @Test
- fun hasCustomPositionUpdatedAnimation_withConfigTrue_isTrue() =
- testScope.runTest {
- with(kosmos) {
- keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
- fakeKeyguardClockRepository.setCurrentClock(
- buildClockController(hasCustomPositionUpdatedAnimation = true)
- )
- }
-
- val hasCustomPositionUpdatedAnimation by
- collectLastValue(underTest.hasCustomPositionUpdatedAnimation)
- assertThat(hasCustomPositionUpdatedAnimation).isEqualTo(true)
- }
-
- @Test
- fun hasCustomPositionUpdatedAnimation_withConfigFalse_isFalse() =
- testScope.runTest {
- with(kosmos) {
- keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
- fakeKeyguardClockRepository.setCurrentClock(
- buildClockController(hasCustomPositionUpdatedAnimation = false)
- )
- }
-
- val hasCustomPositionUpdatedAnimation by
- collectLastValue(underTest.hasCustomPositionUpdatedAnimation)
- assertThat(hasCustomPositionUpdatedAnimation).isEqualTo(false)
- }
-
- private fun buildClockController(
- hasCustomPositionUpdatedAnimation: Boolean = false
- ): ClockController {
- val clockController = mock(ClockController::class.java)
- val largeClock = mock(ClockFaceController::class.java)
- val config = mock(ClockFaceConfig::class.java)
-
- whenever(clockController.largeClock).thenReturn(largeClock)
- whenever(largeClock.config).thenReturn(config)
- whenever(config.hasCustomPositionUpdatedAnimation)
- .thenReturn(hasCustomPositionUpdatedAnimation)
-
- return clockController
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index b70cc30eb3e1..fe8fdc042ae4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -29,7 +29,9 @@ import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_RESUME
import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
import com.android.systemui.media.controls.ui.controller.MediaPlayerData
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
@@ -48,12 +50,10 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -76,7 +76,6 @@ private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!!
@TestableLooper.RunWithLooper
class MediaDataFilterImplTest : SysuiTestCase() {
- @Mock private lateinit var listener: MediaDataFilterImpl.Listener
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var broadcastSender: BroadcastSender
@Mock private lateinit var mediaDataManager: MediaDataManager
@@ -89,7 +88,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Mock private lateinit var cardAction: SmartspaceAction
private lateinit var mediaDataFilter: MediaDataFilterImpl
- private lateinit var mediaFilterRepository: MediaFilterRepository
+ private lateinit var repository: MediaFilterRepository
private lateinit var testScope: TestScope
private lateinit var dataMain: MediaData
private lateinit var dataGuest: MediaData
@@ -102,7 +101,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
MediaPlayerData.clear()
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
testScope = TestScope()
- mediaFilterRepository = MediaFilterRepository()
+ repository = MediaFilterRepository()
mediaDataFilter =
MediaDataFilterImpl(
context,
@@ -113,10 +112,9 @@ class MediaDataFilterImplTest : SysuiTestCase() {
clock,
logger,
mediaFlags,
- mediaFilterRepository,
+ repository,
)
mediaDataFilter.mediaDataManager = mediaDataManager
- mediaDataFilter.addListener(listener)
// Start all tests as main user
setUser(USER_MAIN)
@@ -162,91 +160,114 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnDataLoadedForCurrentUser_callsListener() {
- // GIVEN a media for main user
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ fun onDataLoadedForCurrentUser_updatesLoadedStates() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val mediaDataLoadingModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
- // THEN we should tell the listener
- verify(listener).onMediaDataLoaded(eq(dataMain.instanceId), eq(true), eq(0), eq(false))
- }
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
+ }
@Test
- fun testOnDataLoadedForGuest_doesNotCallListener() {
- // GIVEN a media for guest user
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
+ fun onDataLoadedForGuest_doesNotUpdateLoadedStates() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
- // THEN we should NOT tell the listener
- verify(listener, never()).onMediaDataLoaded(any(), anyBoolean(), anyInt(), anyBoolean())
- }
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
+
+ assertThat(mediaDataLoadedStates).isNotEqualTo(mediaLoadedStatesModel)
+ }
@Test
- fun testOnRemovedForCurrent_callsListener() {
- // GIVEN a media was removed for main user
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ fun onRemovedForCurrent_updatesLoadedStates() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val mediaLoadedStatesModel =
+ mutableListOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
- // THEN we should tell the listener
- verify(listener).onMediaDataRemoved(eq(dataMain.instanceId))
- }
+ // GIVEN a media was removed for main user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+
+ mediaLoadedStatesModel.remove(MediaDataLoadingModel.Loaded(dataMain.instanceId))
+ mediaDataFilter.onMediaDataRemoved(KEY)
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+ }
@Test
- fun testOnRemovedForGuest_doesNotCallListener() {
- // GIVEN a media was removed for guest user
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ fun onRemovedForGuest_doesNotUpdateLoadedStates() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
- // THEN we should NOT tell the listener
- verify(listener, never()).onMediaDataRemoved(eq(dataGuest.instanceId))
- }
+ // GIVEN a media was removed for guest user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
+ mediaDataFilter.onMediaDataRemoved(KEY)
+
+ assertThat(mediaDataLoadedStates).isEmpty()
+ }
@Test
- fun testOnUserSwitched_removesOldUserControls() {
- // GIVEN that we have a media loaded for main user
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ fun onUserSwitched_removesOldUserControls() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
- // and we switch to guest user
- setUser(USER_GUEST)
+ // GIVEN that we have a media loaded for main user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
- // THEN we should remove the main user's media
- verify(listener).onMediaDataRemoved(eq(dataMain.instanceId))
- }
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+
+ // and we switch to guest user
+ setUser(USER_GUEST)
+
+ // THEN we should remove the main user's media
+ assertThat(mediaDataLoadedStates).isEmpty()
+ }
@Test
- fun testOnUserSwitched_addsNewUserControls() {
- // GIVEN that we had some media for both users
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
- mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataGuest)
- reset(listener)
+ fun onUserSwitched_addsNewUserControls() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val guestLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataGuest.instanceId))
+ val mainLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
- // and we switch to guest user
- setUser(USER_GUEST)
+ // GIVEN that we had some media for both users
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataGuest)
- // THEN we should add back the guest user media
- verify(listener).onMediaDataLoaded(eq(dataGuest.instanceId), eq(true), eq(0), eq(false))
+ // and we switch to guest user
+ setUser(USER_GUEST)
- // but not the main user's
- verify(listener, never())
- .onMediaDataLoaded(eq(dataMain.instanceId), anyBoolean(), anyInt(), anyBoolean())
- }
+ assertThat(mediaDataLoadedStates).isEqualTo(guestLoadedStatesModel)
+ assertThat(mediaDataLoadedStates).isNotEqualTo(mainLoadedStatesModel)
+ }
@Test
- fun testOnProfileChanged_profileUnavailable_loadControls() {
- // GIVEN that we had some media for both profiles
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
- mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataPrivateProfile)
- reset(listener)
+ fun onProfileChanged_profileUnavailable_updateStates() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
- // and we change profile status
- setPrivateProfileUnavailable()
+ // GIVEN that we had some media for both profiles
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataPrivateProfile)
- // THEN we should add the private profile media
- verify(listener).onMediaDataRemoved(eq(dataPrivateProfile.instanceId))
- }
+ // and we change profile status
+ setPrivateProfileUnavailable()
+
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
+ // THEN we should remove the private profile media
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+ }
@Test
fun hasAnyMedia_mediaSet_returnsTrue() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
assertThat(hasAnyMedia(selectedUserEntries)).isTrue()
@@ -255,7 +276,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasAnyMedia_recommendationSet_returnsFalse() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
assertThat(hasAnyMedia(selectedUserEntries)).isFalse()
@@ -264,8 +285,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasAnyMediaOrRecommendation_mediaSet_returnsTrue() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
@@ -275,8 +296,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasAnyMediaOrRecommendation_recommendationSet_returnsTrue() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
@@ -286,7 +307,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasActiveMedia_inactiveMediaSet_returnsFalse() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
val data = dataMain.copy(active = false)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
@@ -297,7 +318,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasActiveMedia_activeMediaSet_returnsTrue() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
val data = dataMain.copy(active = true)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
@@ -307,9 +328,9 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasActiveMediaOrRecommendation_inactiveMediaSet_returnsFalse() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
val data = dataMain.copy(active = false)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
@@ -326,9 +347,9 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasActiveMediaOrRecommendation_activeMediaSet_returnsTrue() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
val data = dataMain.copy(active = true)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
@@ -345,9 +366,9 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasActiveMediaOrRecommendation_inactiveRecommendationSet_returnsFalse() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
whenever(smartspaceData.isActive).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -364,9 +385,9 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasActiveMediaOrRecommendation_invalidRecommendationSet_returnsFalse() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
whenever(smartspaceData.isValid()).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -383,9 +404,9 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasActiveMediaOrRecommendation_activeAndValidRecommendationSet_returnsTrue() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
whenever(smartspaceData.isActive).thenReturn(true)
whenever(smartspaceData.isValid()).thenReturn(true)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -401,10 +422,10 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testHasAnyMediaOrRecommendation_onlyCurrentUser() =
+ fun hasAnyMediaOrRecommendation_onlyCurrentUser() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
.isFalse()
@@ -415,11 +436,11 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testHasActiveMediaOrRecommendation_onlyCurrentUser() =
+ fun hasActiveMediaOrRecommendation_onlyCurrentUser() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -443,10 +464,10 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnNotificationRemoved_doesNotHaveMedia() =
+ fun onNotificationRemoved_doesNotHaveMedia() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
mediaDataFilter.onMediaDataRemoved(KEY)
@@ -456,7 +477,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSwipeToDismiss_setsTimedOut() {
+ fun onSwipeToDismiss_setsTimedOut() {
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
mediaDataFilter.onSwipeToDismiss()
@@ -464,15 +485,19 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataLoaded_noMedia_activeValidRec_prioritizesSmartspace() =
+ fun onSmartspaceMediaDataLoaded_noMedia_activeValidRec_prioritizesSmartspace() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel =
+ SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY, isPrioritized = true)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(true))
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -487,18 +512,19 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataLoaded_noMedia_inactiveRec_showsNothing() =
+ fun onSmartspaceMediaDataLoaded_noMedia_inactiveRec_showsNothing() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
whenever(smartspaceData.isActive).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- verify(listener, never()).onMediaDataLoaded(any(), anyBoolean(), anyInt(), anyBoolean())
- verify(listener, never()).onSmartspaceMediaDataLoaded(any(), anyBoolean())
+ assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -513,17 +539,21 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataLoaded_noRecentMedia_activeValidRec_prioritizesSmartspace() =
+ fun onSmartspaceMediaDataLoaded_noRecentMedia_activeValidRec_prioritizesSmartspace() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel =
+ SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY, isPrioritized = true)
val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataOld)
clock.advanceTime(MediaDataFilterImpl.SMARTSPACE_MAX_AGE + 100)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(true))
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -538,11 +568,13 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataLoaded_noRecentMedia_inactiveRec_showsNothing() =
+ fun onSmartspaceMediaDataLoaded_noRecentMedia_inactiveRec_showsNothing() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
whenever(smartspaceData.isActive).thenReturn(false)
val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
@@ -550,7 +582,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
clock.advanceTime(MediaDataFilterImpl.SMARTSPACE_MAX_AGE + 100)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- verify(listener, never()).onSmartspaceMediaDataLoaded(any(), anyBoolean())
+ assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -565,27 +597,29 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_inactiveRec_showsNothing() =
+ fun onSmartspaceMediaDataLoaded_hasRecentMedia_inactiveRec_showsNothing() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
whenever(smartspaceData.isActive).thenReturn(false)
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false))
- reset(listener)
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+
// AND we get a smartspace signal
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- // THEN we should tell listeners to treat the media as not active instead
- verify(listener, never()).onMediaDataLoaded(any(), anyBoolean(), anyInt(), anyBoolean())
- verify(listener, never()).onSmartspaceMediaDataLoaded(any(), anyBoolean())
+ // THEN we should treat the media as not active instead
+ assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -600,27 +634,28 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_activeInvalidRec_usesMedia() =
+ fun onSmartspaceMediaDataLoaded_hasRecentMedia_activeInvalidRec_usesMedia() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
whenever(smartspaceData.isValid()).thenReturn(false)
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false))
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
// AND we get a smartspace signal
runCurrent()
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- // THEN we should tell listeners to treat the media as active instead
- val dataCurrentAndActive = dataCurrent.copy(active = true)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrentAndActive.instanceId), eq(true), eq(100), eq(true))
+ // THEN we should treat the media as active instead
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -630,31 +665,35 @@ class MediaDataFilterImplTest : SysuiTestCase() {
)
.isTrue()
// Smartspace update shouldn't be propagated for the empty rec list.
- verify(listener, never()).onSmartspaceMediaDataLoaded(any(), anyBoolean())
+ assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown)
verify(logger, never()).logRecommendationAdded(any(), any())
verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
}
@Test
- fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_activeValidRec_usesBoth() =
+ fun onSmartspaceMediaDataLoaded_hasRecentMedia_activeValidRec_usesBoth() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
+ val mediaDataLoadingModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
+ val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
+
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false))
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
// AND we get a smartspace signal
runCurrent()
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- // THEN we should tell listeners to treat the media as active instead
- val dataCurrentAndActive = dataCurrent.copy(active = true)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrentAndActive.instanceId), eq(true), eq(100), eq(true))
+ // THEN we should treat the media as active instead
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -664,22 +703,25 @@ class MediaDataFilterImplTest : SysuiTestCase() {
)
.isTrue()
// Smartspace update should also be propagated but not prioritized.
- verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false))
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
}
@Test
- fun testOnSmartspaceMediaDataRemoved_usedSmartspace_clearsSmartspace() =
+ fun onSmartspaceMediaDataRemoved_usedSmartspace_clearsSmartspace() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Removed(SMARTSPACE_KEY)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
- verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -692,26 +734,28 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataRemoved_usedMediaAndSmartspace_clearsBoth() =
+ fun onSmartspaceMediaDataRemoved_usedMediaAndSmartspace_clearsBoth() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Removed(SMARTSPACE_KEY)
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false))
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
runCurrent()
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- val dataCurrentAndActive = dataCurrent.copy(active = true)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrentAndActive.instanceId), eq(true), eq(100), eq(true))
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
- verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -724,17 +768,20 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceLoaded_persistentEnabled_isInactive_notifiesListeners() =
+ fun onSmartspaceLoaded_persistentEnabled_isInactive() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
whenever(smartspaceData.isActive).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false))
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -748,11 +795,16 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceLoaded_persistentEnabled_inactive_hasRecentMedia_staysInactive() =
+ fun onSmartspaceLoaded_persistentEnabled_inactive_hasRecentMedia_staysInactive() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
whenever(smartspaceData.isActive).thenReturn(false)
@@ -760,16 +812,14 @@ class MediaDataFilterImplTest : SysuiTestCase() {
// If there is media that was recently played but inactive
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false))
- reset(listener)
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+
// And an inactive recommendation is loaded
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// Smartspace is loaded but the media stays inactive
- verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false))
- verify(listener, never()).onMediaDataLoaded(any(), anyBoolean(), anyInt(), anyBoolean())
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -783,7 +833,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSwipeToDismiss_persistentEnabled_recommendationSetInactive() {
+ fun onSwipeToDismiss_persistentEnabled_recommendationSetInactive() {
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
val data =
@@ -802,16 +852,21 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testSmartspaceLoaded_shouldTriggerResume_doesTrigger() =
+ fun smartspaceLoaded_shouldTriggerResume_doesTrigger() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false))
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
// AND we get a smartspace signal with extra to trigger resume
runCurrent()
@@ -819,10 +874,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {
whenever(cardAction.extras).thenReturn(extras)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- // THEN we should tell listeners to treat the media as active instead
- val dataCurrentAndActive = dataCurrent.copy(active = true)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrentAndActive.instanceId), eq(true), eq(100), eq(true))
+ // THEN we should treat the media as active instead
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -831,27 +884,33 @@ class MediaDataFilterImplTest : SysuiTestCase() {
)
)
.isTrue()
- // And send the smartspace data, but not prioritized
- verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false))
+ // And update the smartspace data state, but not prioritized
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
}
@Test
- fun testSmartspaceLoaded_notShouldTriggerResume_doesNotTrigger() {
- // WHEN we have media that was recently played, but not currently active
- val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener).onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false))
+ fun smartspaceLoaded_notShouldTriggerResume_doesNotTrigger() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
- // AND we get a smartspace signal with extra to not trigger resume
- val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) }
- whenever(cardAction.extras).thenReturn(extras)
- mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ // WHEN we have media that was recently played, but not currently active
+ val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- // THEN listeners are not updated to show media
- verify(listener, never()).onMediaDataLoaded(any(), eq(true), eq(100), eq(true))
- // But the smartspace update is still propagated
- verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false))
- }
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+
+ // AND we get a smartspace signal with extra to not trigger resume
+ val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) }
+ whenever(cardAction.extras).thenReturn(extras)
+ mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+ // But the smartspace update is still propagated
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
+ }
private fun hasActiveMediaOrRecommendation(
entries: Map<InstanceId, MediaData>?,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 44798ea99bee..253607846e0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -257,6 +257,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
userId = userId,
colorBackground = 0,
isForegroundTask = isForegroundTask,
+ userType = RecentTask.UserType.STANDARD,
+ splitBounds = null,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
index 9b346d0120ef..fa1c8f8ea2c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
@@ -20,15 +20,10 @@ import android.content.ComponentName
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
-import android.graphics.drawable.Drawable
import androidx.test.filters.SmallTest
-import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.FastBitmapDrawable
-import com.android.launcher3.icons.IconFactory
import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.system.PackageManagerWrapper
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -40,20 +35,17 @@ import org.junit.runners.JUnit4
@SmallTest
@RunWith(JUnit4::class)
-class IconLoaderLibAppIconLoaderTest : SysuiTestCase() {
+class BasicPackageManagerAppIconLoaderTest : SysuiTestCase() {
- private val iconFactory: IconFactory = mock()
private val packageManagerWrapper: PackageManagerWrapper = mock()
private val packageManager: PackageManager = mock()
private val dispatcher = Dispatchers.Unconfined
private val appIconLoader =
- IconLoaderLibAppIconLoader(
+ BasicPackageManagerAppIconLoader(
backgroundDispatcher = dispatcher,
- context = context,
packageManagerWrapper = packageManagerWrapper,
packageManager = packageManager,
- iconFactoryProvider = { iconFactory }
)
@Test
@@ -70,12 +62,7 @@ class IconLoaderLibAppIconLoaderTest : SysuiTestCase() {
private fun givenIcon(component: ComponentName, userId: Int, icon: FastBitmapDrawable) {
val activityInfo = mock<ActivityInfo>()
whenever(packageManagerWrapper.getActivityInfo(component, userId)).thenReturn(activityInfo)
- val rawIcon = mock<Drawable>()
- whenever(activityInfo.loadIcon(packageManager)).thenReturn(rawIcon)
-
- val bitmapInfo = mock<BitmapInfo>()
- whenever(iconFactory.createBadgedIconBitmap(eq(rawIcon), any())).thenReturn(bitmapInfo)
- whenever(bitmapInfo.newIcon(context)).thenReturn(icon)
+ whenever(activityInfo.loadIcon(packageManager)).thenReturn(icon)
}
private fun createIcon(): FastBitmapDrawable =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index b593def283ae..6ac86f58517d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -1,9 +1,15 @@
package com.android.systemui.mediaprojection.appselector.data
import android.app.ActivityManager.RecentTaskInfo
+import android.content.pm.UserInfo
+import android.os.UserManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.CLONED
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.PRIVATE
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.STANDARD
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.WORK
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -17,6 +23,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -25,12 +32,16 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() {
private val dispatcher = Dispatchers.Unconfined
private val recentTasks: RecentTasks = mock()
private val userTracker: UserTracker = mock()
+ private val userManager: UserManager = mock {
+ whenever(getUserInfo(anyInt())).thenReturn(mock())
+ }
private val recentTaskListProvider =
ShellRecentTaskListProvider(
dispatcher,
Runnable::run,
Optional.of(recentTasks),
- userTracker
+ userTracker,
+ userManager,
)
@Test
@@ -147,6 +158,22 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() {
.inOrder()
}
+ @Test
+ fun loadRecentTasks_assignsCorrectUserType() {
+ givenRecentTasks(
+ createSingleTask(taskId = 1, userId = 10, userType = STANDARD),
+ createSingleTask(taskId = 2, userId = 20, userType = WORK),
+ createSingleTask(taskId = 3, userId = 30, userType = CLONED),
+ createSingleTask(taskId = 4, userId = 40, userType = PRIVATE),
+ )
+
+ val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+ assertThat(result.map { it.userType })
+ .containsExactly(STANDARD, WORK, CLONED, PRIVATE)
+ .inOrder()
+ }
+
@Suppress("UNCHECKED_CAST")
private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) {
whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer {
@@ -155,7 +182,10 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() {
}
}
- private fun createRecentTask(taskId: Int): RecentTask =
+ private fun createRecentTask(
+ taskId: Int,
+ userType: RecentTask.UserType = STANDARD
+ ): RecentTask =
RecentTask(
taskId = taskId,
displayId = 0,
@@ -164,25 +194,43 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() {
baseIntentComponent = null,
colorBackground = null,
isForegroundTask = false,
+ userType = userType,
+ splitBounds = null
)
- private fun createSingleTask(taskId: Int, isVisible: Boolean = false): GroupedRecentTaskInfo =
- GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, isVisible))
+ private fun createSingleTask(
+ taskId: Int,
+ userId: Int = 0,
+ isVisible: Boolean = false,
+ userType: RecentTask.UserType = STANDARD,
+ ): GroupedRecentTaskInfo {
+ val userInfo =
+ mock<UserInfo> {
+ whenever(isCloneProfile).thenReturn(userType == CLONED)
+ whenever(isManagedProfile).thenReturn(userType == WORK)
+ whenever(isPrivateProfile).thenReturn(userType == PRIVATE)
+ }
+ whenever(userManager.getUserInfo(userId)).thenReturn(userInfo)
+ return GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, userId, isVisible))
+ }
private fun createTaskPair(
taskId1: Int,
+ userId1: Int = 0,
taskId2: Int,
+ userId2: Int = 0,
isVisible: Boolean = false
): GroupedRecentTaskInfo =
GroupedRecentTaskInfo.forSplitTasks(
- createTaskInfo(taskId1, isVisible),
- createTaskInfo(taskId2, isVisible),
+ createTaskInfo(taskId1, userId1, isVisible),
+ createTaskInfo(taskId2, userId2, isVisible),
null
)
- private fun createTaskInfo(taskId: Int, isVisible: Boolean = false) =
+ private fun createTaskInfo(taskId: Int, userId: Int, isVisible: Boolean = false) =
RecentTaskInfo().apply {
this.taskId = taskId
this.isVisible = isVisible
+ this.userId = userId
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
index ac4107359dbc..f4c5ccfc8388 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
@@ -18,22 +18,28 @@ package com.android.systemui.mediaprojection.appselector.view
import android.app.ActivityOptions
import android.app.IActivityTaskManager
+import android.graphics.Rect
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX
+import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN
import com.android.systemui.SysuiTestCase
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.util.mockito.mock
+import com.android.wm.shell.splitscreen.SplitScreen
+import com.android.wm.shell.util.SplitBounds
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
import org.junit.Rule
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
@SmallTest
@@ -46,9 +52,10 @@ class MediaProjectionRecentsViewControllerTest : SysuiTestCase() {
private val taskViewSizeProvider = mock<TaskPreviewSizeProvider>()
private val activityTaskManager = mock<IActivityTaskManager>()
private val resultHandler = mock<MediaProjectionAppSelectorResultHandler>()
+ private val splitScreen = Optional.of(mock<SplitScreen>())
private val bundleCaptor = ArgumentCaptor.forClass(Bundle::class.java)
- private val task =
+ private val fullScreenTask =
RecentTask(
taskId = 123,
displayId = 456,
@@ -56,7 +63,22 @@ class MediaProjectionRecentsViewControllerTest : SysuiTestCase() {
topActivityComponent = null,
baseIntentComponent = null,
colorBackground = null,
- isForegroundTask = false
+ isForegroundTask = false,
+ userType = RecentTask.UserType.STANDARD,
+ splitBounds = null
+ )
+
+ private val splitScreenTask =
+ RecentTask(
+ taskId = 123,
+ displayId = 456,
+ userId = 789,
+ topActivityComponent = null,
+ baseIntentComponent = null,
+ colorBackground = null,
+ isForegroundTask = false,
+ userType = RecentTask.UserType.STANDARD,
+ splitBounds = SplitBounds(Rect(), Rect(), 0, 0, 0)
)
private val taskView =
@@ -69,61 +91,97 @@ class MediaProjectionRecentsViewControllerTest : SysuiTestCase() {
tasksAdapterFactory,
taskViewSizeProvider,
activityTaskManager,
- resultHandler
+ resultHandler,
+ splitScreen,
)
@Test
- fun onRecentAppClicked_taskWithSameIdIsStartedFromRecents() {
- controller.onRecentAppClicked(task, taskView)
+ fun onRecentAppClicked_fullScreenTaskWithSameIdIsStartedFromRecents() {
+ controller.onRecentAppClicked(fullScreenTask, taskView)
- verify(activityTaskManager).startActivityFromRecents(eq(task.taskId), any())
+ verify(activityTaskManager).startActivityFromRecents(eq(fullScreenTask.taskId), any())
+ }
+
+ @Test
+ fun onRecentAppClicked_splitScreenTaskWithSameIdIsStartedFromRecents() {
+ mSetFlagsRule.enableFlags(FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN)
+ controller.onRecentAppClicked(splitScreenTask, taskView)
+
+ verify(splitScreen.get())
+ .startTasks(
+ eq(splitScreenTask.taskId),
+ any(),
+ anyInt(),
+ any(),
+ anyInt(),
+ anyInt(),
+ any(),
+ any()
+ )
}
@Test
fun onRecentAppClicked_launchDisplayIdIsSet() {
- controller.onRecentAppClicked(task, taskView)
+ controller.onRecentAppClicked(fullScreenTask, taskView)
- assertThat(getStartedTaskActivityOptions().launchDisplayId).isEqualTo(task.displayId)
+ assertThat(getStartedTaskActivityOptions(fullScreenTask.taskId).launchDisplayId)
+ .isEqualTo(fullScreenTask.displayId)
}
@Test
- fun onRecentAppClicked_taskNotInForeground_usesScaleUpAnimation() {
- controller.onRecentAppClicked(task, taskView)
+ fun onRecentAppClicked_fullScreenTaskNotInForeground_usesScaleUpAnimation() {
+ assertThat(fullScreenTask.isForegroundTask).isFalse()
+ controller.onRecentAppClicked(fullScreenTask, taskView)
- assertThat(getStartedTaskActivityOptions().animationType)
+ assertThat(getStartedTaskActivityOptions(fullScreenTask.taskId).animationType)
.isEqualTo(ActivityOptions.ANIM_SCALE_UP)
}
@Test
- fun onRecentAppClicked_taskInForeground_flagOff_usesScaleUpAnimation() {
+ fun onRecentAppClicked_fullScreenTaskInForeground_flagOff_usesScaleUpAnimation() {
mSetFlagsRule.disableFlags(FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX)
- controller.onRecentAppClicked(task, taskView)
+ controller.onRecentAppClicked(fullScreenTask, taskView)
- assertThat(getStartedTaskActivityOptions().animationType)
+ assertThat(getStartedTaskActivityOptions(fullScreenTask.taskId).animationType)
.isEqualTo(ActivityOptions.ANIM_SCALE_UP)
}
@Test
- fun onRecentAppClicked_taskInForeground_flagOn_usesDefaultAnimation() {
+ fun onRecentAppClicked_fullScreenTaskInForeground_flagOn_usesDefaultAnimation() {
mSetFlagsRule.enableFlags(FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX)
- val foregroundTask = task.copy(isForegroundTask = true)
+ assertForegroundTaskUsesDefaultCloseAnimation(fullScreenTask)
+ }
+
+ @Test
+ fun onRecentAppClicked_splitScreenTaskInForeground_flagOn_usesDefaultAnimation() {
+ mSetFlagsRule.enableFlags(
+ FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX,
+ FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN
+ )
+ assertForegroundTaskUsesDefaultCloseAnimation(splitScreenTask)
+ }
+ private fun assertForegroundTaskUsesDefaultCloseAnimation(task: RecentTask) {
+ val foregroundTask = task.copy(isForegroundTask = true)
controller.onRecentAppClicked(foregroundTask, taskView)
expect
- .that(getStartedTaskActivityOptions().animationType)
+ .that(getStartedTaskActivityOptions(foregroundTask.taskId).animationType)
.isEqualTo(ActivityOptions.ANIM_CUSTOM)
- expect.that(getStartedTaskActivityOptions().overrideTaskTransition).isTrue()
expect
- .that(getStartedTaskActivityOptions().customExitResId)
+ .that(getStartedTaskActivityOptions(foregroundTask.taskId).overrideTaskTransition)
+ .isTrue()
+ expect
+ .that(getStartedTaskActivityOptions(foregroundTask.taskId).customExitResId)
.isEqualTo(com.android.internal.R.anim.resolver_close_anim)
- expect.that(getStartedTaskActivityOptions().customEnterResId).isEqualTo(0)
+ expect
+ .that(getStartedTaskActivityOptions(foregroundTask.taskId).customEnterResId)
+ .isEqualTo(0)
}
- private fun getStartedTaskActivityOptions(): ActivityOptions {
- verify(activityTaskManager)
- .startActivityFromRecents(eq(task.taskId), bundleCaptor.capture())
+ private fun getStartedTaskActivityOptions(taskId: Int): ActivityOptions {
+ verify(activityTaskManager).startActivityFromRecents(eq(taskId), bundleCaptor.capture())
return ActivityOptions.fromBundle(bundleCaptor.value)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 82ee99a29427..830f08a0c445 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -23,7 +23,7 @@ import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel
import com.android.systemui.statusbar.policy.BluetoothController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 9104f8e43892..6846c7227d9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -88,6 +88,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() {
private lateinit var dialog: SystemUIDialog
private lateinit var factory: SystemUIDialog.Factory
private lateinit var latch: CountDownLatch
+ private var issueRecordingState = IssueRecordingState()
@Before
fun setup() {
@@ -128,6 +129,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() {
mediaProjectionMetricsLogger,
userFileManager,
screenCaptureDisabledDialogDelegate,
+ issueRecordingState,
) {
latch.countDown()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
new file mode 100644
index 000000000000..5e7d8fb5df02
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.Bundle
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.Window
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyBlocking
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ActionExecutorTest : SysuiTestCase() {
+ private val scheduler = TestCoroutineScheduler()
+ private val mainDispatcher = StandardTestDispatcher(scheduler)
+ private val testScope = TestScope(mainDispatcher)
+
+ private val intentExecutor = mock<ActionIntentExecutor>()
+ private val window = mock<Window>()
+ private val view = mock<View>()
+ private val onDismiss = mock<(() -> Unit)>()
+ private val pendingIntent = mock<PendingIntent>()
+
+ private lateinit var actionExecutor: ActionExecutor
+
+ @Test
+ fun startSharedTransition_callsLaunchIntent() = runTest {
+ actionExecutor = createActionExecutor()
+
+ actionExecutor.startSharedTransition(Intent(Intent.ACTION_EDIT), UserHandle.CURRENT, true)
+ scheduler.advanceUntilIdle()
+
+ val intentCaptor = argumentCaptor<Intent>()
+ verifyBlocking(intentExecutor) {
+ launchIntent(capture(intentCaptor), eq(UserHandle.CURRENT), eq(true), any(), any())
+ }
+ assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_EDIT)
+ }
+
+ @Test
+ fun sendPendingIntent_dismisses() = runTest {
+ actionExecutor = createActionExecutor()
+
+ actionExecutor.sendPendingIntent(pendingIntent)
+
+ verify(pendingIntent).send(any(Bundle::class.java))
+ verify(onDismiss).invoke()
+ }
+
+ private fun createActionExecutor(): ActionExecutor {
+ return ActionExecutor(intentExecutor, testScope, window, view, onDismiss)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
index 0c324706857f..5e53fe16534d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
@@ -64,7 +64,7 @@ class ActionIntentExecutorTest : SysuiTestCase() {
val intent = Intent(Intent.ACTION_EDIT).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
val userHandle = myUserHandle()
- actionIntentExecutor.launchIntent(intent, null, userHandle, false)
+ actionIntentExecutor.launchIntent(intent, userHandle, false, null, null)
scheduler.advanceUntilIdle()
verify(activityManagerWrapper)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
index 4a5cf57e6fa8..853e50a12ea5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
@@ -16,65 +16,38 @@
package com.android.systemui.screenshot
-import android.app.ActivityOptions
-import android.app.ExitTransitionCoordinator
-import android.app.Notification
-import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
+import android.os.Process
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.view.accessibility.AccessibilityManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
-import com.android.systemui.clipboardoverlay.EditTextActivity
-import com.android.systemui.res.R
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
-import kotlin.test.Ignore
import kotlin.test.Test
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestCoroutineScheduler
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.kotlin.any
-import org.mockito.kotlin.never
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyBlocking
-import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
@SmallTest
class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
- private val scheduler = TestCoroutineScheduler()
- private val mainDispatcher = StandardTestDispatcher(scheduler)
- private val testScope = TestScope(mainDispatcher)
-
- private val actionIntentExecutor = mock<ActionIntentExecutor>()
+ private val actionExecutor = mock<ActionExecutor>()
private val accessibilityManager = mock<AccessibilityManager>()
private val uiEventLogger = mock<UiEventLogger>()
- private val smartActionsProvider = mock<SmartActionsProvider>()
- private val transition = mock<android.util.Pair<ActivityOptions, ExitTransitionCoordinator>>()
- private val requestDismissal = mock<() -> Unit>()
private val request = ScreenshotData.forTesting()
- private val invalidResult = ScreenshotController.SavedImageData()
- private val validResult =
- ScreenshotController.SavedImageData().apply {
- uri = Uri.EMPTY
- owner = UserHandle.OWNER
- subject = "Test"
- imageTime = 0
- }
+ private val validResult = ScreenshotSavedResult(Uri.EMPTY, Process.myUserHandle(), 0)
private lateinit var viewModel: ScreenshotViewModel
private lateinit var actionsProvider: ScreenshotActionsProvider
@@ -91,7 +64,7 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
assertNotNull(viewModel.previewAction.value)
viewModel.previewAction.value!!.invoke()
- verifyNoMoreInteractions(actionIntentExecutor)
+ verifyNoMoreInteractions(actionExecutor)
}
@Test
@@ -105,39 +78,24 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
assertThat(secondAction.onClicked).isNotNull()
firstAction.onClicked!!.invoke()
secondAction.onClicked!!.invoke()
- verifyNoMoreInteractions(actionIntentExecutor)
+ verifyNoMoreInteractions(actionExecutor)
}
@Test
- fun actionAccessed_withInvalidResult_doesNothing() {
- actionsProvider = createActionsProvider()
-
- actionsProvider.setCompletedScreenshot(invalidResult)
- viewModel.previewAction.value!!.invoke()
- viewModel.actions.value[1].onClicked!!.invoke()
-
- verifyNoMoreInteractions(actionIntentExecutor)
- }
-
- @Test
- @Ignore("b/332526567")
fun actionAccessed_withResult_launchesIntent() = runTest {
actionsProvider = createActionsProvider()
actionsProvider.setCompletedScreenshot(validResult)
viewModel.actions.value[0].onClicked!!.invoke()
- scheduler.advanceUntilIdle()
verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED), eq(0), eq(""))
val intentCaptor = argumentCaptor<Intent>()
- verifyBlocking(actionIntentExecutor) {
- launchIntent(capture(intentCaptor), eq(transition), eq(UserHandle.CURRENT), eq(true))
- }
+ verify(actionExecutor)
+ .startSharedTransition(capture(intentCaptor), eq(Process.myUserHandle()), eq(true))
assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_EDIT)
}
@Test
- @Ignore("b/332526567")
fun actionAccessed_whilePending_launchesMostRecentAction() = runTest {
actionsProvider = createActionsProvider()
@@ -145,59 +103,22 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
viewModel.previewAction.value!!.invoke()
viewModel.actions.value[1].onClicked!!.invoke()
actionsProvider.setCompletedScreenshot(validResult)
- scheduler.advanceUntilIdle()
verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED), eq(0), eq(""))
val intentCaptor = argumentCaptor<Intent>()
- verifyBlocking(actionIntentExecutor) {
- launchIntent(capture(intentCaptor), eq(transition), eq(UserHandle.CURRENT), eq(false))
- }
+ verify(actionExecutor)
+ .startSharedTransition(capture(intentCaptor), eq(Process.myUserHandle()), eq(false))
assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CHOOSER)
}
- @Test
- fun quickShareTapped_wrapsAndSendsIntent() = runTest {
- val quickShare =
- Notification.Action(
- R.drawable.ic_screenshot_edit,
- "TestQuickShare",
- PendingIntent.getActivity(
- context,
- 0,
- Intent(context, EditTextActivity::class.java),
- PendingIntent.FLAG_MUTABLE
- )
- )
- whenever(smartActionsProvider.requestQuickShare(any(), any(), any())).then {
- (it.getArgument(2) as ((Notification.Action) -> Unit)).invoke(quickShare)
- }
- whenever(smartActionsProvider.wrapIntent(any(), any(), any(), any())).thenAnswer {
- it.getArgument(0)
- }
- actionsProvider = createActionsProvider()
-
- viewModel.actions.value[2].onClicked?.invoke()
- verify(uiEventLogger, never())
- .log(eq(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED), any(), any())
- verify(smartActionsProvider, never()).wrapIntent(any(), any(), any(), any())
- actionsProvider.setCompletedScreenshot(validResult)
- verify(smartActionsProvider)
- .wrapIntent(eq(quickShare), eq(validResult.uri), eq(validResult.subject), eq("testid"))
- verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED), eq(0), eq(""))
- }
-
private fun createActionsProvider(): ScreenshotActionsProvider {
return DefaultScreenshotActionsProvider(
context,
viewModel,
- actionIntentExecutor,
- smartActionsProvider,
uiEventLogger,
- testScope,
request,
"testid",
- { transition },
- requestDismissal,
+ actionExecutor
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
index 587da2d5d677..b051df21389e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
@@ -18,11 +18,6 @@ package com.android.systemui.screenshot
import android.app.ActivityTaskManager.RootTaskInfo
import android.app.IActivityTaskManager
-import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
-import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
-import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
-import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.content.ComponentName
import android.content.Context
import android.graphics.Rect
@@ -31,6 +26,12 @@ import android.os.UserManager
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
+import com.android.systemui.screenshot.policy.ActivityType.Home
+import com.android.systemui.screenshot.policy.ActivityType.Undefined
+import com.android.systemui.screenshot.policy.WindowingMode.FullScreen
+import com.android.systemui.screenshot.policy.WindowingMode.PictureInPicture
+import com.android.systemui.screenshot.policy.newChildTask
+import com.android.systemui.screenshot.policy.newRootTaskInfo
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -58,20 +59,19 @@ class ScreenshotPolicyImplTest : SysuiTestCase() {
),
Rect(0, 0, 1080, 2400),
UserHandle.of(MANAGED_PROFILE_USER),
- 65))
+ 65
+ )
+ )
}
@Test
fun findPrimaryContent_ignoresPipTask() = runBlocking {
- val policy = fakeTasksPolicyImpl(
- mContext,
- shadeExpanded = false,
- tasks = listOf(
- pipTask,
- fullScreenWorkProfileTask,
- launcherTask,
- emptyTask)
- )
+ val policy =
+ fakeTasksPolicyImpl(
+ mContext,
+ shadeExpanded = false,
+ tasks = listOf(pipTask, fullScreenWorkProfileTask, launcherTask, emptyTask)
+ )
val info = policy.findPrimaryContent(DISPLAY_ID)
assertThat(info).isEqualTo(fullScreenWorkProfileTask.toDisplayContentInfo())
@@ -79,14 +79,12 @@ class ScreenshotPolicyImplTest : SysuiTestCase() {
@Test
fun findPrimaryContent_shadeExpanded_ignoresTopTask() = runBlocking {
- val policy = fakeTasksPolicyImpl(
- mContext,
- shadeExpanded = true,
- tasks = listOf(
- fullScreenWorkProfileTask,
- launcherTask,
- emptyTask)
- )
+ val policy =
+ fakeTasksPolicyImpl(
+ mContext,
+ shadeExpanded = true,
+ tasks = listOf(fullScreenWorkProfileTask, launcherTask, emptyTask)
+ )
val info = policy.findPrimaryContent(DISPLAY_ID)
assertThat(info).isEqualTo(policy.systemUiContent)
@@ -94,11 +92,7 @@ class ScreenshotPolicyImplTest : SysuiTestCase() {
@Test
fun findPrimaryContent_emptyTaskList() = runBlocking {
- val policy = fakeTasksPolicyImpl(
- mContext,
- shadeExpanded = false,
- tasks = listOf()
- )
+ val policy = fakeTasksPolicyImpl(mContext, shadeExpanded = false, tasks = listOf())
val info = policy.findPrimaryContent(DISPLAY_ID)
assertThat(info).isEqualTo(policy.systemUiContent)
@@ -106,14 +100,12 @@ class ScreenshotPolicyImplTest : SysuiTestCase() {
@Test
fun findPrimaryContent_workProfileNotOnTop() = runBlocking {
- val policy = fakeTasksPolicyImpl(
- mContext,
- shadeExpanded = false,
- tasks = listOf(
- launcherTask,
- fullScreenWorkProfileTask,
- emptyTask)
- )
+ val policy =
+ fakeTasksPolicyImpl(
+ mContext,
+ shadeExpanded = false,
+ tasks = listOf(launcherTask, fullScreenWorkProfileTask, emptyTask)
+ )
val info = policy.findPrimaryContent(DISPLAY_ID)
assertThat(info).isEqualTo(launcherTask.toDisplayContentInfo())
@@ -129,102 +121,80 @@ class ScreenshotPolicyImplTest : SysuiTestCase() {
val dispatcher = Dispatchers.Unconfined
val displayTracker = FakeDisplayTracker(context)
- return object : ScreenshotPolicyImpl(context, userManager, atmService, dispatcher,
- displayTracker) {
+ return object :
+ ScreenshotPolicyImpl(context, userManager, atmService, dispatcher, displayTracker) {
override suspend fun isManagedProfile(userId: Int) = (userId == MANAGED_PROFILE_USER)
override suspend fun getAllRootTaskInfosOnDisplay(displayId: Int) = tasks
override suspend fun isNotificationShadeExpanded() = shadeExpanded
}
}
- private val pipTask = RootTaskInfo().apply {
- configuration.windowConfiguration.apply {
- windowingMode = WINDOWING_MODE_PINNED
- setBounds(Rect(628, 1885, 1038, 2295))
- activityType = ACTIVITY_TYPE_STANDARD
+ private val pipTask =
+ newRootTaskInfo(
+ taskId = 66,
+ userId = PRIMARY_USER,
+ displayId = DISPLAY_ID,
+ bounds = Rect(628, 1885, 1038, 2295),
+ windowingMode = PictureInPicture,
+ topActivity = ComponentName.unflattenFromString(YOUTUBE_PIP_ACTIVITY),
+ ) {
+ listOf(newChildTask(taskId = 66, userId = 0, name = YOUTUBE_HOME_ACTIVITY))
}
- displayId = DISPLAY_ID
- userId = PRIMARY_USER
- taskId = 66
- visible = true
- isVisible = true
- isRunning = true
- numActivities = 1
- topActivity = ComponentName(
- "com.google.android.youtube",
- "com.google.android.apps.youtube.app.watchwhile.WatchWhileActivity"
- )
- childTaskIds = intArrayOf(66)
- childTaskNames = arrayOf("com.google.android.youtube/" +
- "com.google.android.youtube.app.honeycomb.Shell\$HomeActivity")
- childTaskUserIds = intArrayOf(0)
- childTaskBounds = arrayOf(Rect(628, 1885, 1038, 2295))
- }
- private val fullScreenWorkProfileTask = RootTaskInfo().apply {
- configuration.windowConfiguration.apply {
- windowingMode = WINDOWING_MODE_FULLSCREEN
- setBounds(Rect(0, 0, 1080, 2400))
- activityType = ACTIVITY_TYPE_STANDARD
+ private val fullScreenWorkProfileTask =
+ newRootTaskInfo(
+ taskId = 65,
+ userId = MANAGED_PROFILE_USER,
+ displayId = DISPLAY_ID,
+ bounds = Rect(0, 0, 1080, 2400),
+ windowingMode = FullScreen,
+ topActivity = ComponentName.unflattenFromString(FILES_HOME_ACTIVITY),
+ ) {
+ listOf(
+ newChildTask(taskId = 65, userId = MANAGED_PROFILE_USER, name = FILES_HOME_ACTIVITY)
+ )
}
- displayId = DISPLAY_ID
- userId = MANAGED_PROFILE_USER
- taskId = 65
- visible = true
- isVisible = true
- isRunning = true
- numActivities = 1
- topActivity = ComponentName(
- "com.google.android.apps.nbu.files",
- "com.google.android.apps.nbu.files.home.HomeActivity"
- )
- childTaskIds = intArrayOf(65)
- childTaskNames = arrayOf("com.google.android.apps.nbu.files/" +
- "com.google.android.apps.nbu.files.home.HomeActivity")
- childTaskUserIds = intArrayOf(MANAGED_PROFILE_USER)
- childTaskBounds = arrayOf(Rect(0, 0, 1080, 2400))
- }
-
- private val launcherTask = RootTaskInfo().apply {
- configuration.windowConfiguration.apply {
- windowingMode = WINDOWING_MODE_FULLSCREEN
- setBounds(Rect(0, 0, 1080, 2400))
- activityType = ACTIVITY_TYPE_HOME
+ private val launcherTask =
+ newRootTaskInfo(
+ taskId = 1,
+ userId = PRIMARY_USER,
+ displayId = DISPLAY_ID,
+ activityType = Home,
+ windowingMode = FullScreen,
+ bounds = Rect(0, 0, 1080, 2400),
+ topActivity = ComponentName.unflattenFromString(LAUNCHER_ACTIVITY),
+ ) {
+ listOf(newChildTask(taskId = 1, userId = 0, name = LAUNCHER_ACTIVITY))
}
- displayId = DISPLAY_ID
- taskId = 1
- userId = PRIMARY_USER
- visible = true
- isVisible = true
- isRunning = true
- numActivities = 1
- topActivity = ComponentName(
- "com.google.android.apps.nexuslauncher",
- "com.google.android.apps.nexuslauncher.NexusLauncherActivity",
- )
- childTaskIds = intArrayOf(1)
- childTaskNames = arrayOf("com.google.android.apps.nexuslauncher/" +
- "com.google.android.apps.nexuslauncher.NexusLauncherActivity")
- childTaskUserIds = intArrayOf(0)
- childTaskBounds = arrayOf(Rect(0, 0, 1080, 2400))
- }
- private val emptyTask = RootTaskInfo().apply {
- configuration.windowConfiguration.apply {
- windowingMode = WINDOWING_MODE_FULLSCREEN
- setBounds(Rect(0, 0, 1080, 2400))
- activityType = ACTIVITY_TYPE_UNDEFINED
+ private val emptyTask =
+ newRootTaskInfo(
+ taskId = 2,
+ userId = PRIMARY_USER,
+ displayId = DISPLAY_ID,
+ visible = false,
+ running = false,
+ numActivities = 0,
+ activityType = Undefined,
+ bounds = Rect(0, 0, 1080, 2400),
+ ) {
+ listOf(
+ newChildTask(taskId = 3, name = ""),
+ newChildTask(taskId = 4, name = ""),
+ )
}
- displayId = DISPLAY_ID
- taskId = 2
- userId = PRIMARY_USER
- visible = false
- isVisible = false
- isRunning = false
- numActivities = 0
- childTaskIds = intArrayOf(3, 4)
- childTaskNames = arrayOf("", "")
- childTaskUserIds = intArrayOf(0, 0)
- childTaskBounds = arrayOf(Rect(0, 0, 1080, 2400), Rect(0, 2400, 1080, 4800))
- }
}
+
+const val YOUTUBE_HOME_ACTIVITY =
+ "com.google.android.youtube/" + "com.google.android.youtube.app.honeycomb.Shell\$HomeActivity"
+
+const val FILES_HOME_ACTIVITY =
+ "com.google.android.apps.nbu.files/" + "com.google.android.apps.nbu.files.home.HomeActivity"
+
+const val YOUTUBE_PIP_ACTIVITY =
+ "com.google.android.youtube/" +
+ "com.google.android.apps.youtube.app.watchwhile.WatchWhileActivity"
+
+const val LAUNCHER_ACTIVITY =
+ "com.google.android.apps.nexuslauncher/" +
+ "com.google.android.apps.nexuslauncher.NexusLauncherActivity"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
new file mode 100644
index 000000000000..6c35b233ffec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.app.ActivityTaskManager.RootTaskInfo
+import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
+import android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM
+import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.content.ComponentName
+import android.graphics.Rect
+import android.os.UserHandle
+import android.view.Display
+import com.android.systemui.screenshot.data.model.ChildTaskModel
+import com.android.systemui.screenshot.policy.ActivityType.Standard
+import com.android.systemui.screenshot.policy.WindowingMode.FullScreen
+
+/** An enum mapping to [android.app.WindowConfiguration] constants via [toInt]. */
+enum class ActivityType(private val intValue: Int) {
+ Undefined(ACTIVITY_TYPE_UNDEFINED),
+ Standard(ACTIVITY_TYPE_STANDARD),
+ Home(ACTIVITY_TYPE_HOME),
+ Recents(ACTIVITY_TYPE_RECENTS),
+ Assistant(ACTIVITY_TYPE_ASSISTANT),
+ Dream(ACTIVITY_TYPE_DREAM);
+
+ /** Returns the [android.app.WindowConfiguration] int constant for the type. */
+ fun toInt() = intValue
+}
+
+/** An enum mapping to [android.app.WindowConfiguration] constants via [toInt]. */
+enum class WindowingMode(private val intValue: Int) {
+ Undefined(WINDOWING_MODE_UNDEFINED),
+ FullScreen(WINDOWING_MODE_FULLSCREEN),
+ PictureInPicture(WINDOWING_MODE_PINNED),
+ Freeform(WINDOWING_MODE_FREEFORM),
+ MultiWindow(WINDOWING_MODE_MULTI_WINDOW);
+
+ /** Returns the [android.app.WindowConfiguration] int constant for the mode. */
+ fun toInt() = intValue
+}
+
+/**
+ * Constructs a child task for a [RootTaskInfo], copying [RootTaskInfo.bounds] and
+ * [RootTaskInfo.userId] from the parent by default.
+ */
+fun RootTaskInfo.newChildTask(
+ taskId: Int,
+ name: String,
+ bounds: Rect? = null,
+ userId: Int? = null
+): ChildTaskModel {
+ return ChildTaskModel(taskId, name, bounds ?: this.bounds, userId ?: this.userId)
+}
+
+/** Constructs a new [RootTaskInfo]. */
+fun newRootTaskInfo(
+ taskId: Int,
+ userId: Int = UserHandle.USER_SYSTEM,
+ displayId: Int = Display.DEFAULT_DISPLAY,
+ visible: Boolean = true,
+ running: Boolean = true,
+ activityType: ActivityType = Standard,
+ windowingMode: WindowingMode = FullScreen,
+ bounds: Rect? = null,
+ topActivity: ComponentName? = null,
+ topActivityType: ActivityType = Standard,
+ numActivities: Int? = null,
+ childTaskListBuilder: RootTaskInfo.() -> List<ChildTaskModel>,
+): RootTaskInfo {
+ return RootTaskInfo().apply {
+ configuration.windowConfiguration.apply {
+ setWindowingMode(windowingMode.toInt())
+ setActivityType(activityType.toInt())
+ setBounds(bounds)
+ }
+ this.bounds = bounds
+ this.displayId = displayId
+ this.userId = userId
+ this.taskId = taskId
+ this.visible = visible
+ this.isVisible = visible
+ this.isRunning = running
+ this.topActivity = topActivity
+ this.topActivityType = topActivityType.toInt()
+ // NOTE: topActivityInfo is _not_ populated by this code
+
+ val childTasks = childTaskListBuilder(this)
+ this.numActivities = numActivities ?: childTasks.size
+
+ childTaskNames = childTasks.map { it.name }.toTypedArray()
+ childTaskIds = childTasks.map { it.id }.toIntArray()
+ childTaskBounds = childTasks.map { it.bounds }.toTypedArray()
+ childTaskUserIds = childTasks.map { it.userId }.toIntArray()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
new file mode 100644
index 000000000000..d44e26c266fc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.ui.viewmodel
+
+import android.view.accessibility.AccessibilityManager
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.mock
+
+@SmallTest
+class ScreenshotViewModelTest {
+ private val accessibilityManager: AccessibilityManager = mock(AccessibilityManager::class.java)
+ private val appearance = ActionButtonAppearance(null, "Label", "Description")
+ private val onclick = {}
+
+ @Test
+ fun testAddAction() {
+ val viewModel = ScreenshotViewModel(accessibilityManager)
+
+ assertThat(viewModel.actions.value).isEmpty()
+
+ viewModel.addAction(appearance, onclick)
+
+ assertThat(viewModel.actions.value).hasSize(1)
+
+ val added = viewModel.actions.value[0]
+ assertThat(added.appearance).isEqualTo(appearance)
+ assertThat(added.onClicked).isEqualTo(onclick)
+ }
+
+ @Test
+ fun testRemoveAction() {
+ val viewModel = ScreenshotViewModel(accessibilityManager)
+ val firstId = viewModel.addAction(ActionButtonAppearance(null, "", ""), {})
+ val secondId = viewModel.addAction(appearance, onclick)
+
+ assertThat(viewModel.actions.value).hasSize(2)
+ assertThat(firstId).isNotEqualTo(secondId)
+
+ viewModel.removeAction(firstId)
+
+ assertThat(viewModel.actions.value).hasSize(1)
+
+ val remaining = viewModel.actions.value[0]
+ assertThat(remaining.appearance).isEqualTo(appearance)
+ assertThat(remaining.onClicked).isEqualTo(onclick)
+ }
+
+ @Test
+ fun testUpdateActionAppearance() {
+ val viewModel = ScreenshotViewModel(accessibilityManager)
+ val id = viewModel.addAction(appearance, onclick)
+ val otherAppearance = ActionButtonAppearance(null, "Other", "Other")
+
+ viewModel.updateActionAppearance(id, otherAppearance)
+
+ assertThat(viewModel.actions.value).hasSize(1)
+ val updated = viewModel.actions.value[0]
+ assertThat(updated.appearance).isEqualTo(otherAppearance)
+ assertThat(updated.onClicked).isEqualTo(onclick)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index 25ba09a0ce90..6a22d8648d91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -84,7 +84,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(mirrorController.toggleSlider).thenReturn(mirror)
+ whenever(mirrorController.getToggleSlider()).thenReturn(mirror)
whenever(motionEvent.copy()).thenReturn(motionEvent)
whenever(vibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(intArrayOf(0))
@@ -129,7 +129,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() {
@Test
fun testNullMirrorNotTrackingTouch() {
- whenever(mirrorController.toggleSlider).thenReturn(null)
+ whenever(mirrorController.getToggleSlider()).thenReturn(null)
mController.setMirrorControllerAndMirror(mirrorController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index dfe72cf11dcb..e7b29d826a0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -398,7 +398,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mFakeKeyguardRepository = keyguardInteractorDeps.getRepository();
mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository);
mFakeKeyguardClockRepository = new FakeKeyguardClockRepository();
- mKeyguardClockInteractor = new KeyguardClockInteractor(mFakeKeyguardClockRepository);
+ mKeyguardClockInteractor = mKosmos.getKeyguardClockInteractor();
mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
mShadeRepository = new FakeShadeRepository();
mShadeAnimationInteractor = new ShadeAnimationInteractorLegacyImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index dfbb699ed915..2c0a15dd4e5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -69,7 +69,6 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
@@ -87,6 +86,8 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import kotlin.test.assertEquals
+import java.util.Optional
import org.mockito.Mockito.`when` as whenever
@OptIn(ExperimentalCoroutinesApi::class)
@@ -138,6 +139,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
private val notificationLaunchAnimationInteractor =
NotificationLaunchAnimationInteractor(notificationLaunchAnimationRepository)
+ private lateinit var falsingCollector: FalsingCollectorFake
private lateinit var fakeClock: FakeSystemClock
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
private lateinit var interactionEventHandler: InteractionEventHandler
@@ -170,11 +172,12 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
testScope = TestScope()
+ falsingCollector = FalsingCollectorFake()
fakeClock = FakeSystemClock()
underTest =
NotificationShadeWindowViewController(
lockscreenShadeTransitionController,
- FalsingCollectorFake(),
+ falsingCollector,
sysuiStatusBarStateController,
dockManager,
notificationShadeDepthController,
@@ -566,6 +569,13 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
verify(sysUIKeyEventHandler).interceptMediaKey(keyEvent)
}
+ @Test
+ fun forwardsCollectKeyEvent() {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A)
+ interactionEventHandler.collectKeyEvent(keyEvent)
+ assertEquals(keyEvent, falsingCollector.lastKeyEvent)
+ }
+
companion object {
private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
index f2abb909e004..7c33648e08a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -1,15 +1,14 @@
package com.android.systemui.shade.transition
-import android.platform.test.annotations.DisableFlags
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -70,7 +69,7 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
+ @DisableSceneContainer
fun onPanelExpansionChanged_setsFractionEqualToEventFraction() {
underTest.onPanelExpansionChanged(DEFAULT_EXPANSION_EVENT)
@@ -78,7 +77,7 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
+ @DisableSceneContainer
fun onPanelStateChanged_forwardsToScrimTransitionController() {
startLegacyPanelExpansion()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index de6108632153..d2fc087e44bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -19,12 +19,10 @@
package com.android.systemui.statusbar
import android.animation.ObjectAnimator
-import android.platform.test.annotations.DisableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -35,6 +33,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.jank.interactionJankMonitor
@@ -187,7 +186,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableSceneContainer
fun testChangeState_logged() {
TestableLooper.get(this).runWithLooper {
underTest.state = StatusBarState.KEYGUARD
@@ -214,7 +213,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableSceneContainer
fun testSetState_appliesState_sameStateButDifferentUpcomingState() {
underTest.state = StatusBarState.SHADE
underTest.setUpcomingState(StatusBarState.KEYGUARD)
@@ -227,7 +226,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableSceneContainer
fun testSetState_appliesState_differentStateEqualToUpcomingState() {
underTest.state = StatusBarState.SHADE
underTest.setUpcomingState(StatusBarState.KEYGUARD)
@@ -239,7 +238,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableSceneContainer
fun testSetState_doesNotApplyState_currentAndUpcomingStatesSame() {
underTest.state = StatusBarState.SHADE
underTest.setUpcomingState(StatusBarState.SHADE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index 54108642385f..edab9d9f7fdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -50,7 +50,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
systemClock,
uiEventLogger,
userTracker,
- avalancheProvider
+ avalancheProvider,
+ systemSettings
)
}
@@ -82,7 +83,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
fun testAvalancheFilter_duringAvalanche_allowConversationFromAfterEvent() {
avalancheProvider.startTime = whenAgo(10)
- withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
ensurePeekState()
assertShouldHeadsUp(buildEntry {
importance = NotificationManager.IMPORTANCE_HIGH
@@ -97,7 +98,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
fun testAvalancheFilter_duringAvalanche_suppressConversationFromBeforeEvent() {
avalancheProvider.startTime = whenAgo(10)
- withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
ensurePeekState()
assertShouldNotHeadsUp(buildEntry {
importance = NotificationManager.IMPORTANCE_DEFAULT
@@ -112,7 +113,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
fun testAvalancheFilter_duringAvalanche_allowHighPriorityConversation() {
avalancheProvider.startTime = whenAgo(10)
- withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
ensurePeekState()
assertShouldHeadsUp(buildEntry {
importance = NotificationManager.IMPORTANCE_HIGH
@@ -125,7 +126,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
fun testAvalancheFilter_duringAvalanche_allowCall() {
avalancheProvider.startTime = whenAgo(10)
- withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
ensurePeekState()
assertShouldHeadsUp(buildEntry {
importance = NotificationManager.IMPORTANCE_HIGH
@@ -138,7 +139,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
fun testAvalancheFilter_duringAvalanche_allowCategoryReminder() {
avalancheProvider.startTime = whenAgo(10)
- withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
ensurePeekState()
assertShouldHeadsUp(buildEntry {
importance = NotificationManager.IMPORTANCE_HIGH
@@ -151,7 +152,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
fun testAvalancheFilter_duringAvalanche_allowCategoryEvent() {
avalancheProvider.startTime = whenAgo(10)
- withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
ensurePeekState()
assertShouldHeadsUp(buildEntry {
importance = NotificationManager.IMPORTANCE_HIGH
@@ -164,7 +165,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
fun testAvalancheFilter_duringAvalanche_allowFsi() {
avalancheProvider.startTime = whenAgo(10)
- withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
assertFsiNotSuppressed()
}
}
@@ -173,7 +174,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
fun testAvalancheFilter_duringAvalanche_allowColorized() {
avalancheProvider.startTime = whenAgo(10)
- withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
ensurePeekState()
assertShouldHeadsUp(buildEntry {
importance = NotificationManager.IMPORTANCE_HIGH
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 24f670831193..3b979a7c1386 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -77,6 +77,8 @@ import com.android.systemui.util.FakeEventLog
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeGlobalSettings
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.SystemSettings
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.utils.leaks.FakeBatteryController
import com.android.systemui.utils.leaks.FakeKeyguardStateController
@@ -126,6 +128,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
protected val uiEventLogger = UiEventLoggerFake()
protected val userTracker = FakeUserTracker()
protected val avalancheProvider: AvalancheProvider = mock()
+ lateinit var systemSettings: SystemSettings
protected abstract val provider: VisualInterruptionDecisionProvider
@@ -153,6 +156,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
deviceProvisionedController.currentUser = userId
userTracker.set(listOf(user), /* currentUserIndex = */ 0)
+ systemSettings = FakeSettings()
provider.start()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
index 620ad9c19bfa..60aaa646fced 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
@@ -30,6 +30,7 @@ import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.EventLog
import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SystemSettings
import com.android.systemui.util.time.SystemClock
object VisualInterruptionDecisionProviderTestUtil {
@@ -51,7 +52,8 @@ object VisualInterruptionDecisionProviderTestUtil {
systemClock: SystemClock,
uiEventLogger: UiEventLogger,
userTracker: UserTracker,
- avalancheProvider: AvalancheProvider
+ avalancheProvider: AvalancheProvider,
+ systemSettings: SystemSettings
): VisualInterruptionDecisionProvider {
return if (VisualInterruptionRefactor.isEnabled) {
VisualInterruptionDecisionProviderImpl(
@@ -70,7 +72,8 @@ object VisualInterruptionDecisionProviderTestUtil {
systemClock,
uiEventLogger,
userTracker,
- avalancheProvider
+ avalancheProvider,
+ systemSettings
)
} else {
NotificationInterruptStateProviderWrapper(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 69e0db9c5e7a..54a6523d82a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -138,7 +138,7 @@ public class NotificationLoggerTest extends SysuiTestCase {
mHeadsUpManager,
mPowerInteractor,
mActiveNotificationsInteractor,
- mKosmos.getFakeSceneContainerFlags(),
+ mKosmos.getSceneContainerFlags(),
() -> mKosmos.getSceneInteractor());
mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
index 33a838ed5183..4b0b4b89fad4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
@@ -25,6 +25,7 @@ import android.testing.AndroidTestingRunner
import android.util.StatsEvent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.assertLogsWtf
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -133,8 +134,10 @@ class NotificationMemoryLoggerTest : SysuiTestCase() {
val pipeline: NotifPipeline = mock()
whenever(pipeline.allNotifs).thenThrow(RuntimeException("Something broke!"))
val logger = NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
- assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
- .isEqualTo(StatsManager.PULL_SKIP)
+ assertLogsWtf {
+ assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
+ .isEqualTo(StatsManager.PULL_SKIP)
+ }
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index fe0d9d06c8f4..db053d842bdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -23,7 +23,6 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
-import static com.android.systemui.concurrency.FakeExecutorKosmosKt.getFakeExecutor;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
import static junit.framework.Assert.assertNotNull;
@@ -97,7 +96,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.wmshell.BubblesManager;
import org.junit.Before;
@@ -182,7 +180,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
mHeadsUpManager,
PowerInteractorFactory.create().getPowerInteractor(),
mActiveNotificationsInteractor,
- mKosmos.getFakeSceneContainerFlags(),
+ mKosmos.getSceneContainerFlags(),
() -> mKosmos.getSceneInteractor()
);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index abb9432425bc..1e058cac8001 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -20,7 +20,6 @@ import static android.view.View.GONE;
import static android.view.WindowInsets.Type.ime;
import static com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION;
-import static com.android.systemui.Flags.FLAG_SCENE_CONTAINER;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL;
@@ -72,6 +71,7 @@ import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.ExpandHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
@@ -227,7 +227,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
+ @DisableSceneContainer // TODO(b/312473478): address disabled test
public void testUpdateStackHeight_qsExpansionZero() {
final float expansionFraction = 0.2f;
final float overExpansion = 50f;
@@ -726,7 +726,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address lack of QS Header
+ @DisableSceneContainer // TODO(b/312473478): address lack of QS Header
public void testInsideQSHeader_noOffset() {
ViewGroup qsHeader = mock(ViewGroup.class);
Rect boundsOnScreen = new Rect(0, 0, 1000, 1000);
@@ -743,7 +743,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address lack of QS Header
+ @DisableSceneContainer // TODO(b/312473478): address lack of QS Header
public void testInsideQSHeader_Offset() {
ViewGroup qsHeader = mock(ViewGroup.class);
Rect boundsOnScreen = new Rect(100, 100, 1000, 1000);
@@ -763,14 +763,14 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
+ @DisableSceneContainer // TODO(b/312473478): address disabled test
public void setFractionToShade_recomputesStackHeight() {
mStackScroller.setFractionToShade(1f);
verify(mNotificationStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat());
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
+ @DisableSceneContainer // TODO(b/312473478): address disabled test
public void testSetOwnScrollY_shadeNotClosing_scrollYChanges() {
// Given: shade is not closing, scrollY is 0
mAmbientState.setScrollY(0);
@@ -869,7 +869,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
+ @DisableSceneContainer // TODO(b/312473478): address disabled test
public void testSplitShade_hasTopOverscroll() {
mTestableResources
.addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
@@ -942,7 +942,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
+ @DisableSceneContainer // TODO(b/312473478): address disabled test
public void testSetMaxDisplayedNotifications_notifiesListeners() {
ExpandableView.OnHeightChangedListener listener =
mock(ExpandableView.OnHeightChangedListener.class);
@@ -957,7 +957,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableSceneContainer
public void testDispatchTouchEvent_sceneContainerDisabled() {
MotionEvent event = MotionEvent.obtain(
SystemClock.uptimeMillis(),
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 ed29665c6e16..25e4728725e9 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
@@ -25,6 +25,8 @@ import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -119,9 +121,9 @@ import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelView;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -179,7 +181,9 @@ import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.MessageRouterImpl;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.FakeGlobalSettings;
+import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;
import com.android.systemui.volume.VolumeComponent;
@@ -323,6 +327,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
+ private final SystemSettings mSystemSettings = new FakeSettings();
private final FakeEventLog mFakeEventLog = new FakeEventLog();
private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
private final FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock);
@@ -331,7 +336,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
private final DumpManager mDumpManager = new DumpManager();
private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager);
- private final SceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags();
+ private final FakeSceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags();
+
+ private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
+ mKosmos.getBrightnessMirrorShowingInteractor();
@Before
public void setup() throws Exception {
@@ -370,7 +378,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mFakeSystemClock,
mock(UiEventLogger.class),
mUserTracker,
- mAvalancheProvider);
+ mAvalancheProvider,
+ mSystemSettings);
mVisualInterruptionDecisionProvider.start();
mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
@@ -553,7 +562,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mUserTracker,
() -> mFingerprintManager,
mActivityStarter,
- mSceneContainerFlags
+ mSceneContainerFlags,
+ mBrightnessMirrorShowingInteractor
);
mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
mCentralSurfaces.initShadeVisibilityListener();
@@ -1084,6 +1094,34 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
verify(mStatusBarWindowController).refreshStatusBarHeight();
}
+ @Test
+ public void brightnesShowingChanged_flagEnabled_ScrimControllerNotified() {
+ mSceneContainerFlags.setEnabled(true);
+ mCentralSurfaces.registerCallbacks();
+
+ mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
+ mTestScope.getTestScheduler().runCurrent();
+ verify(mScrimController).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+
+ mBrightnessMirrorShowingInteractor.setMirrorShowing(false);
+ mTestScope.getTestScheduler().runCurrent();
+ ArgumentCaptor<ScrimState> captor = ArgumentCaptor.forClass(ScrimState.class);
+ // The default is to call the one with the callback argument
+ verify(mScrimController).transitionTo(captor.capture(), any());
+ assertThat(captor.getValue()).isNotEqualTo(ScrimState.BRIGHTNESS_MIRROR);
+ }
+
+ @Test
+ public void brightnesShowingChanged_flagDisabled_ScrimControllerNotified() {
+ mSceneContainerFlags.setEnabled(false);
+ mCentralSurfaces.registerCallbacks();
+
+ mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
+ mTestScope.getTestScheduler().runCurrent();
+ verify(mScrimController, never()).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+ verify(mScrimController, never()).transitionTo(eq(ScrimState.BRIGHTNESS_MIRROR), any());
+ }
+
/**
* Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
* to reconfigure the keyguard to reflect the requested showing/occluded states.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index c1ef1ad9eef9..3e9006e5268c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.platform.test.annotations.DisableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -46,6 +47,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.Clock;
@@ -157,6 +159,7 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
public void testHeaderUpdated() {
mRow.setPinned(true);
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 05fd63e96089..dc3db4c7fa19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -172,7 +172,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
mKeyguardRepository,
mCommandQueue,
PowerInteractorFactory.create().getPowerInteractor(),
- mKosmos.getFakeSceneContainerFlags(),
+ mKosmos.getSceneContainerFlags(),
new FakeKeyguardBouncerRepository(),
new ConfigurationInteractor(new FakeConfigurationRepository()),
new FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 34605fed7d28..f8c01e7c5275 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -76,8 +76,6 @@ import com.android.systemui.bouncer.ui.BouncerView;
import com.android.systemui.bouncer.ui.BouncerViewDelegate;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -145,7 +143,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
@Mock private DreamOverlayStateController mDreamOverlayStateController;
@Mock private LatencyTracker mLatencyTracker;
- private FakeFeatureFlags mFeatureFlags;
@Mock private KeyguardSecurityModel mKeyguardSecurityModel;
@Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
@Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@@ -188,11 +185,10 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
.thenReturn(mKeyguardMessageAreaController);
when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback);
- mFeatureFlags = new FakeFeatureFlags();
- mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false);
mSetFlagsRule.disableFlags(
com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR,
- com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+ com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT
);
when(mNotificationShadeWindowController.getWindowRootView())
@@ -218,7 +214,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
() -> mShadeController,
mLatencyTracker,
mKeyguardSecurityModel,
- mFeatureFlags,
mPrimaryBouncerCallbackInteractor,
mPrimaryBouncerInteractor,
mBouncerView,
@@ -728,7 +723,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
() -> mShadeController,
mLatencyTracker,
mKeyguardSecurityModel,
- mFeatureFlags,
mPrimaryBouncerCallbackInteractor,
mPrimaryBouncerInteractor,
mBouncerView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index ae3425678abd..69536c5e9c0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -30,7 +30,7 @@ import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository
@@ -60,7 +60,7 @@ class KeyguardStatusBarViewModelTest : SysuiTestCase() {
keyguardRepository,
mock<CommandQueue>(),
PowerInteractorFactory.create().powerInteractor,
- kosmos.fakeSceneContainerFlags,
+ kosmos.sceneContainerFlags,
FakeKeyguardBouncerRepository(),
ConfigurationInteractor(FakeConfigurationRepository()),
FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
index 7a83cfe852d6..6f589418cf1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
@@ -23,14 +23,8 @@ import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.model.SysUiStateTest
-import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState
-import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.EASE_IN
-import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.EASE_OUT
-import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.MAIN
-import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.NOT_PLAYING
-import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationStateChangedCallback
-import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.PaintDrawCallback
-import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.RenderEffectDrawCallback
+import com.android.systemui.surfaceeffects.PaintDrawCallback
+import com.android.systemui.surfaceeffects.RenderEffectDrawCallback
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader
import com.google.common.truth.Truth.assertThat
@@ -50,8 +44,8 @@ class LoadingEffectTest : SysUiStateTest() {
var paintFromCallback: Paint? = null
val drawCallback =
object : PaintDrawCallback {
- override fun onDraw(loadingPaint: Paint) {
- paintFromCallback = loadingPaint
+ override fun onDraw(paint: Paint) {
+ paintFromCallback = paint
}
}
val loadingEffect =
@@ -75,8 +69,8 @@ class LoadingEffectTest : SysUiStateTest() {
var renderEffectFromCallback: RenderEffect? = null
val drawCallback =
object : RenderEffectDrawCallback {
- override fun onDraw(loadingRenderEffect: RenderEffect) {
- renderEffectFromCallback = loadingRenderEffect
+ override fun onDraw(renderEffect: RenderEffect) {
+ renderEffectFromCallback = renderEffect
}
}
val loadingEffect =
@@ -98,16 +92,19 @@ class LoadingEffectTest : SysUiStateTest() {
@Test
fun play_animationStateChangesInOrder() {
val config = TurbulenceNoiseAnimationConfig()
- val states = mutableListOf(NOT_PLAYING)
+ val states = mutableListOf(LoadingEffect.AnimationState.NOT_PLAYING)
val stateChangedCallback =
- object : AnimationStateChangedCallback {
- override fun onStateChanged(oldState: AnimationState, newState: AnimationState) {
+ object : LoadingEffect.AnimationStateChangedCallback {
+ override fun onStateChanged(
+ oldState: LoadingEffect.AnimationState,
+ newState: LoadingEffect.AnimationState
+ ) {
states.add(newState)
}
}
val drawCallback =
object : PaintDrawCallback {
- override fun onDraw(loadingPaint: Paint) {}
+ override fun onDraw(paint: Paint) {}
}
val loadingEffect =
LoadingEffect(
@@ -125,7 +122,14 @@ class LoadingEffectTest : SysUiStateTest() {
animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong())
animatorTestRule.advanceTimeBy(500)
- assertThat(states).containsExactly(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING)
+ assertThat(states)
+ .containsExactly(
+ LoadingEffect.AnimationState.NOT_PLAYING,
+ LoadingEffect.AnimationState.EASE_IN,
+ LoadingEffect.AnimationState.MAIN,
+ LoadingEffect.AnimationState.EASE_OUT,
+ LoadingEffect.AnimationState.NOT_PLAYING
+ )
}
@Test
@@ -133,16 +137,22 @@ class LoadingEffectTest : SysUiStateTest() {
val config = TurbulenceNoiseAnimationConfig()
var numPlay = 0
val stateChangedCallback =
- object : AnimationStateChangedCallback {
- override fun onStateChanged(oldState: AnimationState, newState: AnimationState) {
- if (oldState == NOT_PLAYING && newState == EASE_IN) {
+ object : LoadingEffect.AnimationStateChangedCallback {
+ override fun onStateChanged(
+ oldState: LoadingEffect.AnimationState,
+ newState: LoadingEffect.AnimationState
+ ) {
+ if (
+ oldState == LoadingEffect.AnimationState.NOT_PLAYING &&
+ newState == LoadingEffect.AnimationState.EASE_IN
+ ) {
numPlay++
}
}
}
val drawCallback =
object : PaintDrawCallback {
- override fun onDraw(loadingPaint: Paint) {}
+ override fun onDraw(paint: Paint) {}
}
val loadingEffect =
LoadingEffect(
@@ -172,9 +182,15 @@ class LoadingEffectTest : SysUiStateTest() {
}
var isFinished = false
val stateChangedCallback =
- object : AnimationStateChangedCallback {
- override fun onStateChanged(oldState: AnimationState, newState: AnimationState) {
- if (oldState == EASE_OUT && newState == NOT_PLAYING) {
+ object : LoadingEffect.AnimationStateChangedCallback {
+ override fun onStateChanged(
+ oldState: LoadingEffect.AnimationState,
+ newState: LoadingEffect.AnimationState
+ ) {
+ if (
+ oldState == LoadingEffect.AnimationState.EASE_OUT &&
+ newState == LoadingEffect.AnimationState.NOT_PLAYING
+ ) {
isFinished = true
}
}
@@ -205,13 +221,19 @@ class LoadingEffectTest : SysUiStateTest() {
val config = TurbulenceNoiseAnimationConfig(maxDuration = 1000f)
val drawCallback =
object : PaintDrawCallback {
- override fun onDraw(loadingPaint: Paint) {}
+ override fun onDraw(paint: Paint) {}
}
var isFinished = false
val stateChangedCallback =
- object : AnimationStateChangedCallback {
- override fun onStateChanged(oldState: AnimationState, newState: AnimationState) {
- if (oldState == MAIN && newState == NOT_PLAYING) {
+ object : LoadingEffect.AnimationStateChangedCallback {
+ override fun onStateChanged(
+ oldState: LoadingEffect.AnimationState,
+ newState: LoadingEffect.AnimationState
+ ) {
+ if (
+ oldState == LoadingEffect.AnimationState.MAIN &&
+ newState == LoadingEffect.AnimationState.NOT_PLAYING
+ ) {
isFinished = true
}
}
@@ -242,7 +264,7 @@ class LoadingEffectTest : SysUiStateTest() {
)
val drawCallback =
object : PaintDrawCallback {
- override fun onDraw(loadingPaint: Paint) {}
+ override fun onDraw(paint: Paint) {}
}
val loadingEffect =
LoadingEffect(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
index 1125d41856c6..0eba21ada789 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
@@ -16,9 +16,12 @@
package com.android.systemui.wallpapers;
+import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
@@ -32,6 +35,7 @@ import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -77,6 +81,7 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
private Executor mBackgroundExecutor;
private int mColorsProcessed;
+ private int mLocalColorsProcessed;
private int mMiniBitmapUpdatedCount;
private int mActivatedCount;
private int mDeactivatedCount;
@@ -93,6 +98,7 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
private void resetCounters() {
mColorsProcessed = 0;
+ mLocalColorsProcessed = 0;
mMiniBitmapUpdatedCount = 0;
mActivatedCount = 0;
mDeactivatedCount = 0;
@@ -112,10 +118,14 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
new Object(),
new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
@Override
+ public void onColorsProcessed() {
+ mColorsProcessed++;
+ }
+ @Override
public void onColorsProcessed(List<RectF> regions,
List<WallpaperColors> colors) {
assertThat(regions.size()).isEqualTo(colors.size());
- mColorsProcessed += regions.size();
+ mLocalColorsProcessed += regions.size();
}
@Override
@@ -148,8 +158,10 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
.when(spyColorExtractor)
.createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
- doReturn(new WallpaperColors(Color.valueOf(0), Color.valueOf(0), Color.valueOf(0)))
- .when(spyColorExtractor).getLocalWallpaperColors(any(Rect.class));
+ WallpaperColors colors = new WallpaperColors(
+ Color.valueOf(0), Color.valueOf(0), Color.valueOf(0));
+ doReturn(colors).when(spyColorExtractor).getLocalWallpaperColors(any(Rect.class));
+ doReturn(colors).when(spyColorExtractor).getWallpaperColors(any(Bitmap.class), anyFloat());
return spyColorExtractor;
}
@@ -244,7 +256,7 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
assertThat(mActivatedCount).isEqualTo(1);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
- assertThat(mColorsProcessed).isEqualTo(regions.size());
+ assertThat(mLocalColorsProcessed).isEqualTo(regions.size());
spyColorExtractor.removeLocalColorAreas(regions);
assertThat(mDeactivatedCount).isEqualTo(1);
@@ -329,12 +341,69 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
spyColorExtractor.onBitmapChanged(newBitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
}
- assertThat(mColorsProcessed).isEqualTo(regions.size());
+ assertThat(mLocalColorsProcessed).isEqualTo(regions.size());
}
spyColorExtractor.removeLocalColorAreas(regions);
assertThat(mDeactivatedCount).isEqualTo(1);
}
+ /**
+ * Test that after the bitmap changes, the colors are computed only if asked via onComputeColors
+ */
+ @Test
+ @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void testRecomputeColors() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onBitmapChanged(bitmap);
+ assertThat(mColorsProcessed).isEqualTo(0);
+ spyColorExtractor.onComputeColors();
+ assertThat(mColorsProcessed).isEqualTo(1);
+ }
+
+ /**
+ * Test that after onComputeColors is called, the colors are computed once the bitmap is loaded
+ */
+ @Test
+ @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void testRecomputeColorsBeforeBitmapLoaded() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onComputeColors();
+ spyColorExtractor.onBitmapChanged(bitmap);
+ assertThat(mColorsProcessed).isEqualTo(1);
+ }
+
+ /**
+ * Test that after the dim changes, the colors are computed if the bitmap is already loaded
+ */
+ @Test
+ @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void testRecomputeColorsOnDimChanged() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onBitmapChanged(bitmap);
+ spyColorExtractor.onDimAmountChanged(0.5f);
+ assertThat(mColorsProcessed).isEqualTo(1);
+ }
+
+ /**
+ * Test that after the dim changes, the colors will be recomputed once the bitmap is loaded
+ */
+ @Test
+ @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void testRecomputeColorsOnDimChangedBeforeBitmapLoaded() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onDimAmountChanged(0.3f);
+ spyColorExtractor.onBitmapChanged(bitmap);
+ assertThat(mColorsProcessed).isEqualTo(1);
+ }
+
@Test
public void testCleanUp() {
resetCounters();
@@ -346,6 +415,6 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
spyColorExtractor.cleanUp();
spyColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
- assertThat(mColorsProcessed).isEqualTo(0);
+ assertThat(mLocalColorsProcessed).isEqualTo(0);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index aabd4e9e79be..c24c86c8cb2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -174,6 +174,7 @@ import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
import com.android.systemui.util.FakeEventLog;
import com.android.systemui.util.settings.FakeGlobalSettings;
+import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -554,7 +555,8 @@ public class BubblesTest extends SysuiTestCase {
mock(SystemClock.class),
mock(UiEventLogger.class),
mock(UserTracker.class),
- mock(AvalancheProvider.class)
+ mock(AvalancheProvider.class),
+ mock(SystemSettings.class)
);
interruptionDecisionProvider.start();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
index 9cbe6337befe..2ae6f542ac3e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.ui.viewmodel
import android.content.applicationContext
+import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
import com.android.systemui.biometrics.domain.interactor.promptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
@@ -30,6 +31,7 @@ val Kosmos.promptViewModel by Fixture {
promptSelectorInteractor = promptSelectorInteractor,
context = applicationContext,
udfpsOverlayInteractor = udfpsOverlayInteractor,
+ biometricStatusInteractor = biometricStatusInteractor,
udfpsUtils = udfpsUtils
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
index c06554573bd7..9ce9ff2faf21 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
@@ -24,6 +24,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
val Kosmos.bouncerInteractor by Fixture {
BouncerInteractor(
@@ -33,5 +34,6 @@ val Kosmos.bouncerInteractor by Fixture {
deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
falsingInteractor = falsingInteractor,
powerInteractor = powerInteractor,
+ sceneInteractor = sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index 0f6c7cf13211..c3dad748064d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -18,6 +18,7 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.app.admin.devicePolicyManager
import android.content.applicationContext
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
@@ -31,7 +32,6 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
-import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.bouncerViewModel by Fixture {
@@ -44,12 +44,12 @@ val Kosmos.bouncerViewModel by Fixture {
simBouncerInteractor = simBouncerInteractor,
authenticationInteractor = authenticationInteractor,
selectedUserInteractor = selectedUserInteractor,
+ devicePolicyManager = devicePolicyManager,
+ bouncerMessageViewModel = bouncerMessageViewModel,
flags = composeBouncerFlags,
selectedUser = userSwitcherViewModel.selectedUser,
users = userSwitcherViewModel.users,
userSwitcherMenu = userSwitcherViewModel.menu,
actionButton = bouncerActionButtonInteractor.actionButton,
- devicePolicyManager = mock(),
- bouncerMessageViewModel = bouncerMessageViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 8866fd31faac..4b6ef373a7ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -23,6 +23,7 @@ import com.android.systemui.communal.data.repository.communalPrefsRepository
import com.android.systemui.communal.data.repository.communalRepository
import com.android.systemui.communal.data.repository.communalWidgetRepository
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
+import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -33,7 +34,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.settings.userTracker
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -54,11 +55,12 @@ val Kosmos.communalInteractor by Fixture {
userTracker = userTracker,
activityStarter = activityStarter,
userManager = userManager,
+ dockManager = fakeDockManager,
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
communalSettingsInteractor = communalSettingsInteractor,
sceneInteractor = sceneInteractor,
- sceneContainerFlags = fakeSceneContainerFlags,
+ sceneContainerFlags = sceneContainerFlags,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
index b4773f69f1c5..cd2710ef8757 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
@@ -17,17 +17,21 @@
package com.android.systemui.communal.domain.interactor
import com.android.systemui.communal.data.repository.communalSettingsRepository
+import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.settings.userTracker
import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.android.systemui.util.mockito.mock
val Kosmos.communalSettingsInteractor by Fixture {
CommunalSettingsInteractor(
bgScope = applicationCoroutineScope,
+ bgExecutor = fakeExecutor,
repository = communalSettingsRepository,
userInteractor = selectedUserInteractor,
+ userTracker = userTracker,
tableLogBuffer = mock(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
index 23967224e43a..e36ddc17e5a8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
@@ -16,6 +16,8 @@
package com.android.systemui.communal.ui.viewmodel
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.dreamingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
@@ -27,9 +29,13 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.communalTransitionViewModel by
Kosmos.Fixture {
CommunalTransitionViewModel(
- glanceableHubToLockscreenTransitionViewModel,
- lockscreenToGlanceableHubTransitionViewModel,
- dreamingToGlanceableHubTransitionViewModel,
- glanceableHubToDreamingTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel =
+ glanceableHubToLockscreenTransitionViewModel,
+ lockscreenToGlanceableHubTransitionViewModel =
+ lockscreenToGlanceableHubTransitionViewModel,
+ dreamToGlanceableHubTransitionViewModel = dreamingToGlanceableHubTransitionViewModel,
+ glanceableHubToDreamTransitionViewModel = glanceableHubToDreamingTransitionViewModel,
+ communalInteractor = communalInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 0fc0a3c20f70..6c3cf91ec751 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -23,6 +23,8 @@ import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
import org.mockito.Mockito.`when` as whenever
/** Creates a mock display. */
@@ -69,12 +71,20 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
get() = pendingDisplayFlow
+ val _defaultDisplayOff: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val defaultDisplayOff: Flow<Boolean>
+ get() = _defaultDisplayOff.asStateFlow()
+
override val displayAdditionEvent: Flow<Display?>
get() = displayAdditionEventFlow
private val _displayChangeEvent = MutableSharedFlow<Int>(replay = 1)
override val displayChangeEvent: Flow<Int> = _displayChangeEvent
suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
+
+ fun setDefaultDisplayOff(defaultDisplayOff: Boolean) {
+ _defaultDisplayOff.value = defaultDisplayOff
+ }
}
@Module
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dock/DockManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java
index 37540621557f..b99310bcbe38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dock/DockManagerFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java
@@ -49,6 +49,7 @@ public class DockManagerFake implements DockManager {
return mDocked;
}
+ /** Sets the docked state */
public void setIsDocked(boolean docked) {
mDocked = docked;
}
@@ -58,6 +59,7 @@ public class DockManagerFake implements DockManager {
return false;
}
+ /** Notifies callbacks of dock state change */
public void setDockEvent(int event) {
mCallback.onEvent(event);
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dock/DockManagerFakeKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFakeKosmos.kt
index 06275fa226a5..06275fa226a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dock/DockManagerFakeKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFakeKosmos.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/BrokenWithSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/BrokenWithSceneContainer.kt
new file mode 100644
index 000000000000..29b088b0afc1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/BrokenWithSceneContainer.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
+
+/**
+ * This is used by [SceneContainerRule] to assert that the test is broken when
+ * [FLAG_SCENE_CONTAINER] is enabled.
+ */
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
+annotation class BrokenWithSceneContainer(val bugId: Int)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/DisableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/DisableSceneContainer.kt
new file mode 100644
index 000000000000..09f34308bdb9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/DisableSceneContainer.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.platform.test.annotations.DisableFlags
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
+
+/**
+ * This includes @[DisableFlags] to work with [SetFlagsRule] to disable all aconfig flags required
+ * by that feature.
+ */
+@DisableFlags(
+ FLAG_SCENE_CONTAINER,
+)
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
+annotation class DisableSceneContainer
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index c1d2ad6e1be3..e83205c5a859 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -18,12 +18,14 @@ package com.android.systemui.flags
import android.platform.test.annotations.EnableFlags
import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN
+import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR
import com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_SYSUI
+import com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
/**
@@ -39,6 +41,8 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR,
FLAG_PREDICTIVE_BACK_SYSUI,
FLAG_SCENE_CONTAINER,
+ FLAG_DEVICE_ENTRY_UDFPS_REFACTOR,
+ FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
)
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
index d6f2f77ca67a..45ea36464194 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
@@ -34,8 +34,9 @@ val Kosmos.fakeFeatureFlagsClassic by
Kosmos.Fixture {
FakeFeatureFlagsClassic().apply {
set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- set(Flags.NSSL_DEBUG_LINES, false)
set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
+ set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
+ set(Flags.NSSL_DEBUG_LINES, false)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerFlagParameterization.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerFlagParameterization.kt
new file mode 100644
index 000000000000..4e24233a6681
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerFlagParameterization.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
+
+/** The name of the one flag to be disabled for OFF parameterization */
+private const val flagNameToDisable = FLAG_SCENE_CONTAINER
+
+/** Cache of the flags to be enabled for ON parameterization */
+private val flagNamesToEnable =
+ EnableSceneContainer::class.java.getAnnotation(EnableFlags::class.java)!!.value.toList()
+
+/**
+ * Provides one or two copies of this [FlagsParameterization]; one which disabled
+ * [FLAG_SCENE_CONTAINER] and if none of the dependencies of it are disabled by this, a second copy
+ * which enables [FLAG_SCENE_CONTAINER] and all the dependencies (just like [EnableSceneContainer]).
+ */
+fun FlagsParameterization.andSceneContainer(): Sequence<FlagsParameterization> = sequence {
+ check(flagNameToDisable !in mOverrides) {
+ "Can't add $flagNameToDisable to FlagsParameterization: $this"
+ }
+ yield(FlagsParameterization(mOverrides + mapOf(flagNameToDisable to false)))
+ if (flagNamesToEnable.all { mOverrides[it] != false }) {
+ // Can't add the parameterization of enabling SceneContainerFlag to a parameterization that
+ // explicitly disables one of the prerequisite flags.
+ yield(FlagsParameterization(mOverrides + flagNamesToEnable.associateWith { true }))
+ }
+}
+
+/**
+ * Doubles (roughly; see below) the given list of [FlagsParameterization] for enabling and disabling
+ * SceneContainerFlag.
+ *
+ * The input parameterization may not define [FLAG_SCENE_CONTAINER].
+ *
+ * Any [FlagsParameterization] which disables any flag that is a dependency of
+ * [FLAG_SCENE_CONTAINER], will not add a state for enabling, and the state will simply be converted
+ * to one which disables. Just like [EnableSceneContainer], enabling will also enable all the other
+ * dependencies. For any flag parameterization where a dependency is disabled, an "enabled"
+ * parameterization is inconsistent, so it will not be added.
+ */
+fun List<FlagsParameterization>.andSceneContainer(): List<FlagsParameterization> =
+ flatMap { it.andSceneContainer() }.toList()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt
index 775ad14d4ad9..9ec1481355a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt
@@ -18,6 +18,7 @@ package com.android.systemui.flags
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import org.junit.Assert
+import org.junit.AssumptionViolatedException
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
@@ -33,10 +34,7 @@ class SceneContainerRule : TestRule {
return object : Statement() {
@Throws(Throwable::class)
override fun evaluate() {
- val hasAnnotation =
- description?.testClass?.getAnnotation(EnableSceneContainer::class.java) !=
- null || description?.getAnnotation(EnableSceneContainer::class.java) != null
- if (hasAnnotation) {
+ if (description.hasAnnotation<EnableSceneContainer>()) {
Assert.assertTrue(
"SceneContainerFlag.isEnabled is false:" +
"\n * Did you forget to add a new aconfig flag dependency in" +
@@ -45,8 +43,31 @@ class SceneContainerRule : TestRule {
SceneContainerFlag.isEnabled
)
}
+ if (
+ description.hasAnnotation<BrokenWithSceneContainer>() &&
+ SceneContainerFlag.isEnabled
+ ) {
+ runCatching { base?.evaluate() }
+ .onFailure { exception ->
+ if (exception is AssumptionViolatedException) {
+ throw AssertionError(
+ "This is marked @BrokenWithSceneContainer, but was skipped.",
+ exception
+ )
+ }
+ throw AssumptionViolatedException("Test is still broken", exception)
+ }
+ throw AssertionError(
+ "HOORAY! You fixed a test that was marked @BrokenWithSceneContainer. " +
+ "Remove the obsolete annotation to fix this failure."
+ )
+ }
base?.evaluate()
}
}
}
+
+ inline fun <reified T : Annotation> Description?.hasAnnotation(): Boolean =
+ this?.testClass?.getAnnotation(T::class.java) != null ||
+ this?.getAnnotation(T::class.java) != null
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
index eba5a11cecdb..4f2310f9972d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
@@ -50,14 +50,25 @@ class FakeKeyguardClockRepository @Inject constructor() : KeyguardClockRepositor
get() = _previewClock
override val clockEventController: ClockEventController
get() = mock()
+ override val shouldForceSmallClock: Boolean
+ get() = _shouldForceSmallClock
+ private var _shouldForceSmallClock: Boolean = false
override fun setClockSize(@ClockSize size: Int) {
_clockSize.value = size
}
+ fun setSelectedClockSize(size: SettingsClockSize) {
+ selectedClockSize.value = size
+ }
+
fun setCurrentClock(clockController: ClockController) {
_currentClock.value = clockController
}
+
+ fun setShouldForceSmallClock(shouldForceSmallClock: Boolean) {
+ _shouldForceSmallClock = shouldForceSmallClock
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
index 75489b617120..8954231a9731 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
@@ -17,8 +17,6 @@
package com.android.systemui.keyguard.data.repository
import android.os.fakeExecutorHandler
-import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
-import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
@@ -34,8 +32,6 @@ val Kosmos.keyguardBlueprintRepository by
setOf(
defaultBlueprint,
splitShadeBlueprint,
- weatherClockBlueprint,
- splitShadeWeatherClockBlueprint,
),
handler = fakeExecutorHandler,
assert = mock<ThreadAssert>(),
@@ -50,22 +46,6 @@ private val defaultBlueprint =
get() = listOf()
}
-private val weatherClockBlueprint =
- object : KeyguardBlueprint {
- override val id: String
- get() = WEATHER_CLOCK_BLUEPRINT_ID
- override val sections: List<KeyguardSection>
- get() = listOf()
- }
-
-private val splitShadeWeatherClockBlueprint =
- object : KeyguardBlueprint {
- override val id: String
- get() = SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
- override val sections: List<KeyguardSection>
- get() = listOf()
- }
-
private val splitShadeBlueprint =
object : KeyguardBlueprint {
override val id: String
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
index 12165cdc5658..d52883eb38af 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
@@ -18,6 +18,22 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.keyguard.data.repository.keyguardClockRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
val Kosmos.keyguardClockInteractor by
- Kosmos.Fixture { KeyguardClockInteractor(keyguardClockRepository) }
+ Kosmos.Fixture {
+ KeyguardClockInteractor(
+ keyguardClockRepository = keyguardClockRepository,
+ applicationScope = applicationCoroutineScope,
+ mediaCarouselInteractor = mediaCarouselInteractor,
+ activeNotificationsInteractor = activeNotificationsInteractor,
+ shadeInteractor = shadeInteractor,
+ keyguardInteractor = keyguardInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
+ headsUpNotificationInteractor = headsUpNotificationInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
new file mode 100644
index 000000000000..2c6d44f10152
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.keyguardDismissActionInteractor by
+ Kosmos.Fixture {
+ KeyguardDismissActionInteractor(
+ repository = keyguardRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ dismissInteractor = keyguardDismissInteractor,
+ applicationScope = testScope.backgroundScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
new file mode 100644
index 000000000000..f33ca95e488d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.keyguardDismissInteractor by
+ Kosmos.Fixture {
+ KeyguardDismissInteractor(
+ trustRepository = trustRepository,
+ keyguardRepository = keyguardRepository,
+ primaryBouncerInteractor = primaryBouncerInteractor,
+ alternateBouncerInteractor = alternateBouncerInteractor,
+ powerInteractor = powerInteractor,
+ selectedUserInteractor = selectedUserInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt
index ffa4133c7269..9774e4aa51a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt
@@ -19,21 +19,19 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor
-import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.keyguardDismissActionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.sysuiStatusBarStateController
-import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.bouncerToGoneFlows by Fixture {
BouncerToGoneFlows(
statusBarStateController = sysuiStatusBarStateController,
primaryBouncerInteractor = mockPrimaryBouncerInteractor,
- keyguardDismissActionInteractor = mock(),
- featureFlags = featureFlagsClassic,
+ keyguardDismissActionInteractor = { keyguardDismissActionInteractor },
shadeInteractor = shadeInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
index 60dd48aeaf61..a048d3cfffca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -26,7 +25,6 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.notif
val Kosmos.keyguardClockViewModel by
Kosmos.Fixture {
KeyguardClockViewModel(
- keyguardInteractor = keyguardInteractor,
keyguardClockInteractor = keyguardClockInteractor,
applicationScope = applicationCoroutineScope,
notifsKeyguardInteractor = notificationsKeyguardInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
index f86e9b7216ce..e6651a44236f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
@@ -20,8 +20,6 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -32,7 +30,5 @@ var Kosmos.occludedToLockscreenTransitionViewModel by Fixture {
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
configurationInteractor = configurationInteractor,
animationFlow = keyguardTransitionAnimationFlow,
- keyguardInteractor = keyguardInteractor,
- keyguardTransitionInteractor = keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt
index 4ecff73f71ed..d6edea29d6f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt
@@ -19,20 +19,18 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor
-import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.keyguardDismissActionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.statusbar.sysuiStatusBarStateController
-import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.primaryBouncerToGoneTransitionViewModel by Fixture {
PrimaryBouncerToGoneTransitionViewModel(
statusBarStateController = sysuiStatusBarStateController,
primaryBouncerInteractor = mockPrimaryBouncerInteractor,
- keyguardDismissActionInteractor = mock(),
- featureFlags = featureFlagsClassic,
+ keyguardDismissActionInteractor = { keyguardDismissActionInteractor },
bouncerToGoneFlows = bouncerToGoneFlows,
animationFlow = keyguardTransitionAnimationFlow,
)
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 1b23296ec4d3..a46d35842cf3 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
@@ -48,7 +48,9 @@ import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.scene.shared.model.sceneDataSource
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
@@ -69,6 +71,7 @@ class KosmosJavaAdapter(
val testScope by lazy { kosmos.testScope }
val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic }
val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
+ val sceneContainerFlags by lazy { kosmos.sceneContainerFlags }
val fakeExecutor by lazy { kosmos.fakeExecutor }
val fakeExecutorHandler by lazy { kosmos.fakeExecutorHandler }
val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
@@ -106,6 +109,7 @@ class KosmosJavaAdapter(
val sharedNotificationContainerInteractor by lazy {
kosmos.sharedNotificationContainerInteractor
}
+ val brightnessMirrorShowingInteractor by lazy { kosmos.brightnessMirrorShowingInteractor }
init {
kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
index df08e4a78178..a654d6fc239a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -18,6 +18,7 @@ package com.android.systemui.qs.ui.adapter
import android.content.Context
import android.view.View
+import com.android.systemui.settings.brightness.MirrorController
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -41,6 +42,9 @@ class FakeQSSceneAdapter(
private val _navBarPadding = MutableStateFlow<Int>(0)
val navBarPadding = _navBarPadding.asStateFlow()
+ var brightnessMirrorController: MirrorController? = null
+ private set
+
override var isQsFullyCollapsed: Boolean = true
override suspend fun inflate(context: Context) {
@@ -64,4 +68,8 @@ class FakeQSSceneAdapter(
override fun requestCloseCustomizer() {
_customizing.value = false
}
+
+ override fun setBrightnessMirrorController(mirrorController: MirrorController?) {
+ brightnessMirrorController = mirrorController
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt
index bae5257a98bd..ded725683e59 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt
@@ -21,7 +21,7 @@ import dagger.Module
import dagger.Provides
class FakeSceneContainerFlags(
- var enabled: Boolean = false,
+ var enabled: Boolean = SceneContainerFlag.isEnabled,
) : SceneContainerFlags {
override fun isEnabled(): Boolean {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
new file mode 100644
index 000000000000..8b7e5d8f54c5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
@@ -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 com.android.systemui.settings
+
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.classifier.falsingManager
+import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.util.time.systemClock
+
+/** This factory creates empty mocks. */
+var Kosmos.brightnessSliderControllerFactory by
+ Kosmos.Fixture<BrightnessSliderController.Factory> {
+ BrightnessSliderController.Factory(
+ falsingManager,
+ uiEventLogger,
+ vibratorHelper,
+ systemClock,
+ activityStarter,
+ )
+ }
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt
index be1f081d5b8f..6db46499cea9 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt
@@ -1,13 +1,11 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * 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
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -15,8 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
--->
-<resources>
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">false</bool>
-</resources>
+
+package com.android.systemui.settings.brightness.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.brightnessMirrorShowingRepository by
+ Kosmos.Fixture { BrightnessMirrorShowingRepository() }
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt
index be1f081d5b8f..8f6b829f0021 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt
@@ -1,13 +1,11 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * 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
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -15,8 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
--->
-<resources>
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">false</bool>
-</resources>
+
+package com.android.systemui.settings.brightness.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.brightness.data.repository.brightnessMirrorShowingRepository
+
+val Kosmos.brightnessMirrorShowingInteractor by
+ Kosmos.Fixture { BrightnessMirrorShowingInteractor(brightnessMirrorShowingRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt
new file mode 100644
index 000000000000..8fb370caee09
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.ui.viewmodel
+
+import android.content.res.mainResources
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+import com.android.systemui.settings.brightnessSliderControllerFactory
+
+val Kosmos.brightnessMirrorViewModel by
+ Kosmos.Fixture {
+ BrightnessMirrorViewModel(
+ brightnessMirrorShowingInteractor,
+ mainResources,
+ brightnessSliderControllerFactory,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/truth/TruthUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/TruthUtils.kt
new file mode 100644
index 000000000000..64fed689d7a9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/TruthUtils.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.truth
+
+import com.google.common.truth.MapSubject
+import com.google.common.truth.Ordered
+
+fun MapSubject.containsEntriesExactly(entry: Pair<*, *>, vararg entries: Pair<*, *>): Ordered =
+ containsExactly(
+ entry.first,
+ entry.second,
+ *entries
+ .asSequence()
+ .flatMap { (key, value) -> sequenceOf(key, value) }
+ .toList()
+ .toTypedArray()
+ )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
index d3410737a432..348a02e1da04 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
@@ -24,6 +24,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import com.android.systemui.volume.panel.domain.TestComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractorImpl
import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
@@ -44,6 +45,8 @@ val Kosmos.componentsFactory: ComponentsFactory by
var Kosmos.componentsLayoutManager: ComponentsLayoutManager by Kosmos.Fixture()
var Kosmos.enabledComponents: Collection<VolumePanelComponentKey> by
Kosmos.Fixture { componentByKey.keys }
+var Kosmos.volumePanelStartables: Set<VolumePanelStartable> by
+ Kosmos.Fixture { emptySet<VolumePanelStartable>() }
val Kosmos.unavailableCriteria: Provider<ComponentAvailabilityCriteria> by
Kosmos.Fixture { Provider { TestComponentAvailabilityCriteria(false) } }
val Kosmos.availableCriteria: Provider<ComponentAvailabilityCriteria> by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt
index b66d7f974eca..d4a72b437fd8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt
@@ -24,8 +24,9 @@ class FakeAncSliceRepository : AncSliceRepository {
private val sliceByWidth = mutableMapOf<Int, MutableStateFlow<Slice?>>()
- override fun ancSlice(width: Int): Flow<Slice?> =
- sliceByWidth.getOrPut(width) { MutableStateFlow(null) }
+ override fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?> {
+ return sliceByWidth.getOrPut(width) { MutableStateFlow(null) }
+ }
fun putSlice(width: Int, slice: Slice?) {
sliceByWidth.getOrPut(width) { MutableStateFlow(null) }.value = slice
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
index 49041ed0d652..e5f5d4e389f1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
@@ -22,10 +22,12 @@ import com.android.systemui.volume.panel.componentsFactory
import com.android.systemui.volume.panel.componentsInteractor
import com.android.systemui.volume.panel.componentsLayoutManager
import com.android.systemui.volume.panel.dagger.VolumePanelComponent
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import com.android.systemui.volume.panel.volumePanelStartables
import kotlinx.coroutines.CoroutineScope
class KosmosVolumePanelComponentFactory(private val kosmos: Kosmos) : VolumePanelComponentFactory {
@@ -41,5 +43,8 @@ class KosmosVolumePanelComponentFactory(private val kosmos: Kosmos) : VolumePane
override fun componentsLayoutManager(): ComponentsLayoutManager =
kosmos.componentsLayoutManager
+
+ override fun volumePanelStartables(): Set<VolumePanelStartable> =
+ kosmos.volumePanelStartables
}
}
diff --git a/packages/overlays/Android.bp b/packages/overlays/Android.bp
index 5e001fba6aa1..5075f6322d1b 100644
--- a/packages/overlays/Android.bp
+++ b/packages/overlays/Android.bp
@@ -30,9 +30,6 @@ phony {
"FontNotoSerifSourceOverlay",
"NavigationBarMode3ButtonOverlay",
"NavigationBarModeGesturalOverlay",
- "NavigationBarModeGesturalOverlayNarrowBack",
- "NavigationBarModeGesturalOverlayWideBack",
- "NavigationBarModeGesturalOverlayExtraWideBack",
"TransparentNavigationBarOverlay",
"NotesRoleEnabledOverlay",
"preinstalled-packages-platform-overlays.xml",
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml
deleted file mode 100644
index ba7bebac16a4..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.internal.systemui.navbar.gestural_extra_wide_back"
- android:versionCode="1"
- android:versionName="1.0">
- <overlay android:targetPackage="android"
- android:category="com.android.internal.navigation_bar_mode"
- android:priority="1"/>
-
- <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
-</manifest> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml
deleted file mode 100644
index 120a4893811f..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Controls the navigation bar interaction mode:
- 0: 3 button mode (back, home, overview buttons)
- 1: 2 button mode (back, home buttons + swipe up for overview)
- 2: gestures only for back, home and overview -->
- <integer name="config_navBarInteractionMode">2</integer>
-
- <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
- Only applies if the device display is not square. -->
- <bool name="config_navBarCanMove">false</bool>
-
- <!-- Controls whether the navigation bar lets through taps. -->
- <bool name="config_navBarTapThrough">true</bool>
-
- <!-- Controls whether the IME renders the back and IME switcher buttons or not. -->
- <bool name="config_imeDrawsImeNavBar">true</bool>
-
- <!-- Controls the size of the back gesture inset. -->
- <dimen name="config_backGestureInset">40dp</dimen>
-
- <!-- Controls whether the navbar needs a scrim with
- {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
- <bool name="config_navBarNeedsScrim">false</bool>
-
- <!-- Controls the opacity of the navigation bar depending on the visibility of the
- various workspace stacks.
- 0 - Nav bar is always opaque when either the freeform stack or docked stack is visible.
- 1 - Nav bar is always translucent when the freeform stack is visible, otherwise always
- opaque.
- 2 - Nav bar is never forced opaque.
- -->
- <integer name="config_navBarOpacityMode">2</integer>
-
- <!-- Controls whether seamless rotation should be allowed even though the navbar can move
- (which normally prevents seamless rotation). -->
- <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
-
- <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
- show. -->
- <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
-
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">true</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
deleted file mode 100644
index 674bc749bc11..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_height">24dp</dimen>
- <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
- <dimen name="navigation_bar_height_landscape">24dp</dimen>
- <!-- Width of the navigation bar when it is placed vertically on the screen -->
- <dimen name="navigation_bar_width">24dp</dimen>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_frame_height">48dp</dimen>
- <!-- The height of the bottom navigation gesture area. -->
- <dimen name="navigation_bar_gesture_height">32dp</dimen>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp
deleted file mode 100644
index f8a56030e0e4..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// Copyright 2019, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-runtime_resource_overlay {
- name: "NavigationBarModeGesturalOverlayNarrowBack",
- theme: "NavigationBarModeGesturalNarrowBack",
- product_specific: true,
-}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml
deleted file mode 100644
index 8de91c0cf8ab..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.internal.systemui.navbar.gestural_narrow_back"
- android:versionCode="1"
- android:versionName="1.0">
- <overlay android:targetPackage="android"
- android:category="com.android.internal.navigation_bar_mode"
- android:priority="1"/>
-
- <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
-</manifest> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml
deleted file mode 100644
index c18d892ec5b0..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Controls the navigation bar interaction mode:
- 0: 3 button mode (back, home, overview buttons)
- 1: 2 button mode (back, home buttons + swipe up for overview)
- 2: gestures only for back, home and overview -->
- <integer name="config_navBarInteractionMode">2</integer>
-
- <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
- Only applies if the device display is not square. -->
- <bool name="config_navBarCanMove">false</bool>
-
- <!-- Controls whether the navigation bar lets through taps. -->
- <bool name="config_navBarTapThrough">true</bool>
-
- <!-- Controls whether the IME renders the back and IME switcher buttons or not. -->
- <bool name="config_imeDrawsImeNavBar">true</bool>
-
- <!-- Controls the size of the back gesture inset. -->
- <dimen name="config_backGestureInset">18dp</dimen>
-
- <!-- Controls whether the navbar needs a scrim with
- {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
- <bool name="config_navBarNeedsScrim">false</bool>
-
- <!-- Controls the opacity of the navigation bar depending on the visibility of the
- various workspace stacks.
- 0 - Nav bar is always opaque when either the freeform stack or docked stack is visible.
- 1 - Nav bar is always translucent when the freeform stack is visible, otherwise always
- opaque.
- 2 - Nav bar is never forced opaque.
- -->
- <integer name="config_navBarOpacityMode">2</integer>
-
- <!-- Controls whether seamless rotation should be allowed even though the navbar can move
- (which normally prevents seamless rotation). -->
- <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
-
- <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
- show. -->
- <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
-
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">true</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
deleted file mode 100644
index 674bc749bc11..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_height">24dp</dimen>
- <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
- <dimen name="navigation_bar_height_landscape">24dp</dimen>
- <!-- Width of the navigation bar when it is placed vertically on the screen -->
- <dimen name="navigation_bar_width">24dp</dimen>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_frame_height">48dp</dimen>
- <!-- The height of the bottom navigation gesture area. -->
- <dimen name="navigation_bar_gesture_height">32dp</dimen>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml
deleted file mode 100644
index bbab5e0477f7..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Name of overlay [CHAR LIMIT=64] -->
- <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml
deleted file mode 100644
index daf461382b28..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-/**
- * Copyright (c) 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.internal.systemui.navbar.gestural_wide_back"
- android:versionCode="1"
- android:versionName="1.0">
- <overlay android:targetPackage="android"
- android:category="com.android.internal.navigation_bar_mode"
- android:priority="1"/>
-
- <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
-</manifest> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml
deleted file mode 100644
index 877b5f82c28e..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Controls the navigation bar interaction mode:
- 0: 3 button mode (back, home, overview buttons)
- 1: 2 button mode (back, home buttons + swipe up for overview)
- 2: gestures only for back, home and overview -->
- <integer name="config_navBarInteractionMode">2</integer>
-
- <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
- Only applies if the device display is not square. -->
- <bool name="config_navBarCanMove">false</bool>
-
- <!-- Controls whether the navigation bar lets through taps. -->
- <bool name="config_navBarTapThrough">true</bool>
-
- <!-- Controls whether the IME renders the back and IME switcher buttons or not. -->
- <bool name="config_imeDrawsImeNavBar">true</bool>
-
- <!-- Controls the size of the back gesture inset. -->
- <dimen name="config_backGestureInset">32dp</dimen>
-
- <!-- Controls whether the navbar needs a scrim with
- {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
- <bool name="config_navBarNeedsScrim">false</bool>
-
- <!-- Controls the opacity of the navigation bar depending on the visibility of the
- various workspace stacks.
- 0 - Nav bar is always opaque when either the freeform stack or docked stack is visible.
- 1 - Nav bar is always translucent when the freeform stack is visible, otherwise always
- opaque.
- 2 - Nav bar is never forced opaque.
- -->
- <integer name="config_navBarOpacityMode">2</integer>
-
- <!-- Controls whether seamless rotation should be allowed even though the navbar can move
- (which normally prevents seamless rotation). -->
- <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
-
- <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
- show. -->
- <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
-
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">true</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
deleted file mode 100644
index 674bc749bc11..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_height">24dp</dimen>
- <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
- <dimen name="navigation_bar_height_landscape">24dp</dimen>
- <!-- Width of the navigation bar when it is placed vertically on the screen -->
- <dimen name="navigation_bar_width">24dp</dimen>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_frame_height">48dp</dimen>
- <!-- The height of the bottom navigation gesture area. -->
- <dimen name="navigation_bar_gesture_height">32dp</dimen>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml
deleted file mode 100644
index bbab5e0477f7..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Name of overlay [CHAR LIMIT=64] -->
- <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
-</resources> \ No newline at end of file
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index a5b28ad550ba..e77f846ffaa6 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -5,6 +5,17 @@
},
{
"name": "RavenwoodBivalentTest_device"
+ },
+ {
+ "name": "SystemUIGoogleTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
}
],
"ravenwood-presubmit": [
diff --git a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp b/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
index 5e66b29b370a..83f756e9a0bf 100644
--- a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
+++ b/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
@@ -42,7 +42,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
ALOGI("%s: JNI_OnLoad", __FILE__);
int res = jniRegisterNativeMethods(env,
- "com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest",
+ "com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest",
sMethods, NELEM(sMethods));
if (res < 0) {
return res;
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
new file mode 100644
index 000000000000..d91e73438fa3
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest;
+
+import static org.junit.Assert.assertEquals;
+
+import android.util.ArrayMap;
+import android.util.Size;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+// Tests for calling simple Android APIs.
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodAndroidApiTest {
+ @Test
+ public void testArrayMapSimple() {
+ final Map<String, String> map = new ArrayMap<>();
+
+ map.put("key1", "value1");
+ assertEquals("value1", map.get("key1"));
+ }
+
+ @Test
+ public void testSizeSimple() {
+ final var size = new Size(1, 2);
+
+ assertEquals(2, size.getHeight());
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
new file mode 100644
index 000000000000..3a24c0e829a4
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodClassRule;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood
+public class RavenwoodClassRuleDeviceOnlyTest {
+ @ClassRule
+ public static final RavenwoodClassRule sRavenwood = new RavenwoodClassRule();
+
+ @Test
+ public void testDeviceOnly() {
+ Assert.assertFalse(RavenwoodRule.isOnRavenwood());
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java
new file mode 100644
index 000000000000..aa33dc3392b0
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest;
+
+import android.platform.test.ravenwood.RavenwoodClassRule;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+// TODO: atest RavenwoodBivalentTest_device fails with the following message.
+// `RUNNER ERROR: Instrumentation reported numtests=7 but only ran 6`
+// @android.platform.test.annotations.DisabledOnNonRavenwood
+// Figure it out and then make DisabledOnNonRavenwood support TYPEs as well.
+@Ignore
+public class RavenwoodClassRuleRavenwoodOnlyTest {
+ @ClassRule
+ public static final RavenwoodClassRule sRavenwood = new RavenwoodClassRule();
+
+ @Test
+ public void testRavenwoodOnly() {
+ Assert.assertTrue(RavenwoodRule.isOnRavenwood());
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
index 3b106da74ed4..59467e9d7d14 100644
--- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.platform.test.ravenwood.bivalenttest;
+package com.android.ravenwoodtest.bivalenttest;
import static junit.framework.Assert.assertEquals;
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
index 4b650b4bc5e1..3edca7ea5016 100644
--- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.platform.test.ravenwood.bivalenttest;
+package com.android.ravenwoodtest.bivalenttest;
import android.platform.test.annotations.DisabledOnNonRavenwood;
import android.platform.test.annotations.DisabledOnRavenwood;
diff --git a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
index 2cd585ff6c9c..f1e33cb686f1 100644
--- a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
+++ b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.platform.test.ravenwood.coretest;
+package com.android.ravenwoodtest.coretest;
import android.platform.test.ravenwood.RavenwoodRule;
@@ -40,7 +40,7 @@ public class RavenwoodTestRunnerValidationTest {
public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
public RavenwoodTestRunnerValidationTest() {
- Assume.assumeTrue(mRavenwood._ravenwood_private$isOptionalValidationEnabled());
+ Assume.assumeTrue(RavenwoodRule._$RavenwoodPrivate.isOptionalValidationEnabled());
// Because RavenwoodRule will throw this error before executing the test method,
// we can't do it in the test method itself.
// So instead, we initialize it here.
diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
index 8ca34bafc2c6..2fb8074c850a 100644
--- a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
+++ b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
@@ -31,13 +31,17 @@ import java.lang.annotation.Target;
* which means if a test class has this annotation, you can't negate it in subclasses or
* on a per-method basis.
*
+ * THIS ANNOTATION CANNOT BE ADDED TO CLASSES AT THIS PONINT.
+ * See {@link com.android.ravenwoodtest.bivalenttest.RavenwoodClassRuleRavenwoodOnlyTest}
+ * for the reason.
+ *
* The {@code RAVENWOOD_RUN_DISABLED_TESTS} environmental variable won't work because it won't be
* propagated to the device. (We may support it in the future, possibly using a debug. sysprop.)
*
* @hide
*/
@Inherited
-@Target({ElementType.METHOD, ElementType.TYPE})
+@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DisabledOnNonRavenwood {
/**
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
index 9a4d4886d6f0..f4b7ec360dbf 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
@@ -25,6 +25,7 @@ import static android.platform.test.ravenwood.RavenwoodRule.shouldStillIgnoreInP
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.EnabledOnRavenwood;
+import org.junit.Assert;
import org.junit.Assume;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -41,27 +42,16 @@ import org.junit.runners.model.Statement;
public class RavenwoodClassRule implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
- // No special treatment when running outside Ravenwood; run tests as-is
if (!IS_ON_RAVENWOOD) {
- Assume.assumeTrue(shouldEnableOnDevice(description));
- return base;
- }
-
- if (ENABLE_PROBE_IGNORED) {
+ // This should be "Assume", not Assert, but if we use assume here, the device side
+ // test runner would complain.
+ // See the TODO comment in RavenwoodClassRuleRavenwoodOnlyTest.
+ Assert.assertTrue(shouldEnableOnDevice(description));
+ } else if (ENABLE_PROBE_IGNORED) {
Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
- // Pass through to possible underlying RavenwoodRule for both environment
- // configuration and handling method-level annotations
- return base;
} else {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- Assume.assumeTrue(shouldEnableOnRavenwood(description));
- // Pass through to possible underlying RavenwoodRule for both environment
- // configuration and handling method-level annotations
- base.evaluate();
- }
- };
+ Assume.assumeTrue(shouldEnableOnRavenwood(description));
}
+ return base;
}
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 52ea3402fa62..21d8019fcd87 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -28,7 +28,6 @@ import android.platform.test.annotations.DisabledOnNonRavenwood;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.EnabledOnRavenwood;
import android.platform.test.annotations.IgnoreUnderRavenwood;
-import android.util.ArraySet;
import org.junit.Assume;
import org.junit.rules.TestRule;
@@ -278,6 +277,12 @@ public class RavenwoodRule implements TestRule {
return false;
}
}
+ final var clazz = description.getTestClass();
+ if (clazz != null) {
+ if (clazz.getAnnotation(DisabledOnNonRavenwood.class) != null) {
+ return false;
+ }
+ }
return true;
}
@@ -308,14 +313,17 @@ public class RavenwoodRule implements TestRule {
}
// Otherwise, consult any class-level annotations
- if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
- return true;
- }
- if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
- return false;
- }
- if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
- return false;
+ final var clazz = description.getTestClass();
+ if (clazz != null) {
+ if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
+ return true;
+ }
+ if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
+ return false;
+ }
+ if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+ return false;
+ }
}
// When no annotations have been requested, assume test should be included
@@ -413,10 +421,9 @@ public class RavenwoodRule implements TestRule {
};
}
- /**
- * Do not use it outside ravenwood core classes.
- */
- public boolean _ravenwood_private$isOptionalValidationEnabled() {
- return ENABLE_OPTIONAL_VALIDATION;
+ public static class _$RavenwoodPrivate {
+ public static boolean isOptionalValidationEnabled() {
+ return ENABLE_OPTIONAL_VALIDATION;
+ }
}
}
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java
index d02fe69d3168..d566977bd15c 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.ravenwood.mockito;
+package com.android.ravenwoodtest.mockito;
import static com.google.common.truth.Truth.assertThat;
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
index 0c137d5eaacc..aa2b7611da37 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.ravenwood.mockito;
+package com.android.ravenwoodtest.mockito;
import static com.google.common.truth.Truth.assertThat;
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java
index 95667103bd21..fcc6c9cc447d 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.ravenwood.mockito;
+package com.android.ravenwoodtest.mockito;
import static com.google.common.truth.Truth.assertThat;
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java
index 741411095f53..0f65544f8b66 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java
@@ -100,6 +100,16 @@ public class LongArrayMultiStateCounter_host {
mLastStateChangeTimestampMs = timestampMs;
}
+ public void copyStatesFrom(LongArrayMultiStateCounterRavenwood source) {
+ for (int i = 0; i < mStateCount; i++) {
+ mStates[i].mTimeInStateSinceUpdate = source.mStates[i].mTimeInStateSinceUpdate;
+ Arrays.fill(mStates[i].mCounter, 0);
+ }
+ mCurrentState = source.mCurrentState;
+ mLastStateChangeTimestampMs = source.mLastStateChangeTimestampMs;
+ mLastUpdateTimestampMs = source.mLastUpdateTimestampMs;
+ }
+
public void setValue(int state, long[] values) {
System.arraycopy(values, 0, mStates[state].mCounter, 0, mArrayLength);
}
@@ -335,6 +345,10 @@ public class LongArrayMultiStateCounter_host {
getInstance(instanceId).setState(state, timestampMs);
}
+ public static void native_copyStatesFrom(long targetInstanceId, long sourceInstanceId) {
+ getInstance(targetInstanceId).copyStatesFrom(getInstance(sourceInstanceId));
+ }
+
public static void native_incrementValues(long instanceId, long containerInstanceId,
long timestampMs) {
getInstance(instanceId).incrementValues(
diff --git a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java
index efe468d0df25..f833782bc8bb 100644
--- a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java
+++ b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.ravenwood;
+package com.android.ravenwoodtest.servicestest;
import static org.junit.Assert.assertEquals;
diff --git a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
index c1dee5d2f55b..044239f06297 100644
--- a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java
+++ b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.ravenwood;
+package com.android.ravenwoodtest.servicestest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md
index 7c0cee812996..2ab43bbeaad0 100644
--- a/ravenwood/test-authors.md
+++ b/ravenwood/test-authors.md
@@ -17,6 +17,7 @@ android_ravenwood_test {
name: "MyTestsRavenwood",
static_libs: [
"androidx.annotation_annotation",
+ "androidx.test.ext.junit",
"androidx.test.rules",
],
srcs: [
@@ -34,7 +35,7 @@ android_ravenwood_test {
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/services/Android.bp b/services/Android.bp
index 881d6e12ddc7..623519521a5a 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -241,6 +241,7 @@ java_library {
libs: [
"android.hidl.manager-V1.0-java",
"framework-tethering.stubs.module_lib",
+ "keepanno-annotations",
"service-art.stubs.system_server",
"service-permission.stubs.system_server",
"service-rkp.stubs.system_server",
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 1d6399e24b2a..bfa1c7bb99d0 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -49,6 +49,13 @@ flag {
}
flag {
+ name: "enable_a11y_checker_logging"
+ namespace: "accessibility"
+ description: "Whether to identify and log app a11y issues."
+ bug: "325420273"
+}
+
+flag {
name: "enable_magnification_joystick"
namespace: "accessibility"
description: "Whether to enable joystick controls for magnification"
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 73584154df3a..8a699ef39280 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1617,9 +1617,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final int displayId = displays[i].getDisplayId();
onDisplayRemoved(displayId);
}
- if (com.android.server.accessibility.Flags.cleanupA11yOverlays()) {
detachAllOverlays();
- }
}
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0811c872d2eb..e64e500c9b65 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2734,10 +2734,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
userState.mComponentNameToServiceMap;
boolean isUnlockingOrUnlocked = mUmi.isUserUnlockingOrUnlocked(userState.mUserId);
+ // Store the list of installed services.
+ mTempComponentNameSet.clear();
for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) {
AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i);
ComponentName componentName = ComponentName.unflattenFromString(
installedService.getId());
+ mTempComponentNameSet.add(componentName);
AccessibilityServiceConnection service = componentNameToServiceMap.get(componentName);
@@ -2797,6 +2800,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
audioManager.setAccessibilityServiceUids(mTempIntArray);
}
mActivityTaskManagerService.setAccessibilityServiceUids(mTempIntArray);
+
+ // If any services have been removed, remove them from the enabled list and the touch
+ // exploration granted list.
+ boolean anyServiceRemoved =
+ userState.mEnabledServices.removeIf((comp) -> !mTempComponentNameSet.contains(comp))
+ || userState.mTouchExplorationGrantedServices.removeIf(
+ (comp) -> !mTempComponentNameSet.contains(comp));
+ if (anyServiceRemoved) {
+ // Update the enabled services setting.
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userState.mEnabledServices,
+ userState.mUserId);
+ // Update the touch exploration granted services setting.
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+ userState.mTouchExplorationGrantedServices,
+ userState.mUserId);
+ }
updateAccessibilityEnabledSettingLocked(userState);
}
@@ -4153,8 +4175,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
public void enableShortcutsForTargets(
boolean enable, @UserShortcutType int shortcutTypes,
@NonNull List<String> shortcutTargets, @UserIdInt int userId) {
- mContext.enforceCallingPermission(
- Manifest.permission.MANAGE_ACCESSIBILITY, "enableShortcutsForTargets");
+ if (android.view.accessibility.Flags.migrateEnableShortcuts()) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_ACCESSIBILITY, "enableShortcutsForTargets");
+ } else {
+ mContext.enforceCallingPermission(
+ Manifest.permission.MANAGE_ACCESSIBILITY, "enableShortcutsForTargets");
+ }
for (int shortcutType : USER_SHORTCUT_TYPES) {
if ((shortcutTypes & shortcutType) == shortcutType) {
enableShortcutForTargets(enable, shortcutType, shortcutTargets, userId);
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 0a3906a2d07d..ced10fbeff0c 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -1,5 +1,4 @@
package: "android.service.autofill"
-container: "system"
flag {
name: "test"
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 1dc3b73d2bd3..c130ceef1e08 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -1,5 +1,4 @@
package: "android.service.autofill"
-container: "system"
flag {
name: "autofill_credman_integration"
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index ce9cdc2aeab5..0353d5a962c7 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1510,46 +1510,74 @@ public class BackupManagerService extends IBackupManager.Stub {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
return;
}
- dumpWithoutCheckingPermission(fd, pw, args);
- }
- @VisibleForTesting
- void dumpWithoutCheckingPermission(FileDescriptor fd, PrintWriter pw, String[] args) {
- int userId = binderGetCallingUserId();
- if (!isUserReadyForBackup(userId)) {
- pw.println("Inactive");
+ int argIndex = 0;
+
+ String op = nextArg(args, argIndex);
+ argIndex++;
+
+ if ("--help".equals(op)) {
+ showDumpUsage(pw);
return;
}
-
- if (args != null) {
- for (String arg : args) {
- if ("-h".equals(arg)) {
- pw.println("'dumpsys backup' optional arguments:");
- pw.println(" -h : this help text");
- pw.println(" a[gents] : dump information about defined backup agents");
- pw.println(" transportclients : dump information about transport clients");
- pw.println(" transportstats : dump transport statts");
- pw.println(" users : dump the list of users for which backup service "
- + "is running");
- return;
- } else if ("users".equals(arg.toLowerCase())) {
- pw.print(DUMP_RUNNING_USERS_MESSAGE);
- for (int i = 0; i < mUserServices.size(); i++) {
- pw.print(" " + mUserServices.keyAt(i));
- }
- pw.println();
- return;
+ if ("users".equals(op)) {
+ pw.print(DUMP_RUNNING_USERS_MESSAGE);
+ for (int i = 0; i < mUserServices.size(); i++) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i),
+ "dump()");
+ if (userBackupManagerService != null) {
+ pw.print(" " + userBackupManagerService.getUserId());
}
}
+ pw.println();
+ return;
}
-
- for (int i = 0; i < mUserServices.size(); i++) {
+ if ("--user".equals(op)) {
+ String userArg = nextArg(args, argIndex);
+ argIndex++;
+ if (userArg == null) {
+ showDumpUsage(pw);
+ return;
+ }
+ int userId = UserHandle.parseUserArg(userArg);
UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()");
+ getServiceForUserIfCallerHasPermission(userId, "dump()");
if (userBackupManagerService != null) {
userBackupManagerService.dump(fd, pw, args);
}
+ return;
}
+ if (op == null || "agents".startsWith(op) || "transportclients".equals(op)
+ || "transportstats".equals(op)) {
+ for (int i = 0; i < mUserServices.size(); i++) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()");
+ if (userBackupManagerService != null) {
+ userBackupManagerService.dump(fd, pw, args);
+ }
+ }
+ return;
+ }
+
+ showDumpUsage(pw);
+ }
+
+ private String nextArg(String[] args, int argIndex) {
+ if (argIndex >= args.length) {
+ return null;
+ }
+ return args[argIndex];
+ }
+
+ private static void showDumpUsage(PrintWriter pw) {
+ pw.println("'dumpsys backup' optional arguments:");
+ pw.println(" --help : this help text");
+ pw.println(" a[gents] : dump information about defined backup agents");
+ pw.println(" transportclients : dump information about transport clients");
+ pw.println(" transportstats : dump transport stats");
+ pw.println(" users : dump the list of users for which backup service is running");
+ pw.println(" --user <userId> : dump information for user userId");
}
/**
@@ -1661,7 +1689,7 @@ public class BackupManagerService extends IBackupManager.Stub {
* @param message A message to include in the exception if it is thrown.
*/
void enforceCallingPermissionOnUserId(@UserIdInt int userId, String message) {
- if (Binder.getCallingUserHandle().getIdentifier() != userId) {
+ if (binderGetCallingUserId() != userId) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
}
diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
index d7e766eed209..f397814f7ad7 100644
--- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -16,6 +16,8 @@
package com.android.server.companion.utils;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_SCAN;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
@@ -209,7 +211,9 @@ public final class PermissionsUtils {
*/
public static void enforceCallerCanObserveDevicePresenceByUuid(@NonNull Context context) {
if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
- != PERMISSION_GRANTED) {
+ != PERMISSION_GRANTED
+ || context.checkCallingPermission(BLUETOOTH_SCAN) != PERMISSION_GRANTED
+ || context.checkCallingPermission(BLUETOOTH_CONNECT) != PERMISSION_GRANTED) {
throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
+ "permissions to request observing device presence base on the UUID");
}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index f38d772b56c4..23373f1df63c 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -28,6 +28,7 @@ import android.annotation.UserIdInt;
import android.app.WindowConfiguration;
import android.app.compat.CompatChanges;
import android.companion.virtual.VirtualDeviceManager.ActivityListener;
+import android.companion.virtual.flags.Flags;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.AttributionSource;
@@ -298,14 +299,28 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
public boolean canActivityBeLaunched(@NonNull ActivityInfo activityInfo,
@Nullable Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
int launchingFromDisplayId, boolean isNewTask) {
- if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId, isNewTask)) {
- notifyActivityBlocked(activityInfo);
- return false;
- }
- if (mIntentListenerCallback != null && intent != null
- && mIntentListenerCallback.shouldInterceptIntent(intent)) {
- Slog.d(TAG, "Virtual device intercepting intent");
- return false;
+ if (Flags.interceptIntentsBeforeApplyingPolicy()) {
+ if (mIntentListenerCallback != null && intent != null
+ && mIntentListenerCallback.shouldInterceptIntent(intent)) {
+ Slog.d(TAG, "Virtual device intercepting intent");
+ return false;
+ }
+ if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId,
+ isNewTask)) {
+ notifyActivityBlocked(activityInfo);
+ return false;
+ }
+ } else {
+ if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId,
+ isNewTask)) {
+ notifyActivityBlocked(activityInfo);
+ return false;
+ }
+ if (mIntentListenerCallback != null && intent != null
+ && mIntentListenerCallback.shouldInterceptIntent(intent)) {
+ Slog.d(TAG, "Virtual device intercepting intent");
+ return false;
+ }
}
return true;
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index c7d99424f72b..392c0c71c867 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -182,6 +182,7 @@ java_library_static {
"android.hardware.vibrator-V2-java",
"app-compat-annotations",
"framework-tethering.stubs.module_lib",
+ "keepanno-annotations",
"service-art.stubs.system_server",
"service-permission.stubs.system_server",
"service-rkp.stubs.system_server",
@@ -213,7 +214,9 @@ java_library_static {
"android.hardware.health-V3-java", // AIDL
"android.hardware.health-translate-java",
"android.hardware.light-V1-java",
+ "android.hardware.security.authgraph-V1-java",
"android.hardware.security.rkp-V3-java",
+ "android.hardware.security.secretkeeper-V1-java",
"android.hardware.tv.cec-V1.1-java",
"android.hardware.tv.hdmi.cec-V1-java",
"android.hardware.tv.hdmi.connection-V1-java",
@@ -254,6 +257,7 @@ java_library_static {
"net_flags_lib",
"stats_flags_lib",
"core_os_flags_lib",
+ "connectivity_flags_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index ac19d8bc897f..4694e9fd44bc 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -70,6 +70,8 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
NotificationListener mNotificationListener;
@Nullable
private MediaProjectionManager mProjectionManager;
+
+ @GuardedBy("mSensitiveContentProtectionLock")
@Nullable
private MediaProjectionSession mMediaProjectionSession;
@@ -98,6 +100,48 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
mIsExempted = isExempted;
mSessionId = sessionId;
}
+
+ public void logProjectionSessionStart() {
+ FrameworkStatsLog.write(
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
+ mSessionId,
+ mUid,
+ mIsExempted,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
+ );
+ }
+
+ public void logProjectionSessionStop() {
+ FrameworkStatsLog.write(
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
+ mSessionId,
+ mUid,
+ mIsExempted,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
+ );
+ }
+
+ public void logAppBlocked(int uid) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
+ mSessionId,
+ uid,
+ mUid,
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__BLOCKED
+ );
+ }
+
+ public void logAppUnblocked(int uid) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
+ mSessionId,
+ uid,
+ mUid,
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED
+ );
+ }
}
private final MediaProjectionManager.Callback mProjectionCallback =
@@ -112,28 +156,11 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
- FrameworkStatsLog.write(
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
- mMediaProjectionSession.mSessionId,
- mMediaProjectionSession.mUid,
- mMediaProjectionSession.mIsExempted,
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START,
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
- );
}
@Override
public void onStop(MediaProjectionInfo info) {
if (DEBUG) Log.d(TAG, "onStop projection: " + info);
- FrameworkStatsLog.write(
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
- mMediaProjectionSession.mSessionId,
- mMediaProjectionSession.mUid,
- mMediaProjectionSession.mIsExempted,
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP,
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
- );
-
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
"SensitiveContentProtectionManagerService.onProjectionStop");
try {
@@ -242,16 +269,18 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0,
projectionInfo.getUserHandle().getIdentifier());
- mMediaProjectionSession = new MediaProjectionSession(
- uid, isPackageExempted || isFeatureDisabled, new Random().nextLong());
-
- if (isPackageExempted || isFeatureDisabled) {
- Log.w(TAG, "projection session is exempted, package ="
- + projectionInfo.getPackageName() + ", isFeatureDisabled=" + isFeatureDisabled);
- return;
- }
-
synchronized (mSensitiveContentProtectionLock) {
+ mMediaProjectionSession = new MediaProjectionSession(
+ uid, isPackageExempted || isFeatureDisabled, new Random().nextLong());
+ mMediaProjectionSession.logProjectionSessionStart();
+
+ if (isPackageExempted || isFeatureDisabled) {
+ Log.w(TAG, "projection session is exempted, package ="
+ + projectionInfo.getPackageName() + ", isFeatureDisabled="
+ + isFeatureDisabled);
+ return;
+ }
+
mProjectionActive = true;
if (sensitiveNotificationAppProtection()) {
updateAppsThatShouldBlockScreenCapture();
@@ -266,7 +295,10 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
private void onProjectionEnd() {
synchronized (mSensitiveContentProtectionLock) {
mProjectionActive = false;
- mMediaProjectionSession = null;
+ if (mMediaProjectionSession != null) {
+ mMediaProjectionSession.logProjectionSessionStop();
+ mMediaProjectionSession = null;
+ }
// notify windowmanager to clear any sensitive notifications observed during projection
// session
@@ -437,22 +469,14 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
packageInfos.add(packageInfo);
if (isShowingSensitiveContent) {
mWindowManager.addBlockScreenCaptureForApps(packageInfos);
- FrameworkStatsLog.write(
- FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
- mMediaProjectionSession.mSessionId,
- uid,
- mMediaProjectionSession.mUid,
- FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__BLOCKED
- );
+ if (mMediaProjectionSession != null) {
+ mMediaProjectionSession.logAppBlocked(uid);
+ }
} else {
mWindowManager.removeBlockScreenCaptureForApps(packageInfos);
- FrameworkStatsLog.write(
- FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
- mMediaProjectionSession.mSessionId,
- uid,
- mMediaProjectionSession.mUid,
- FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED
- );
+ if (mMediaProjectionSession != null) {
+ mMediaProjectionSession.logAppUnblocked(uid);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 20816a1b22c8..73300e45ed43 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -44,6 +44,9 @@ import com.android.server.am.EventLogTags;
import com.android.server.pm.ApexManager;
import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.TypePattern;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
import dalvik.system.PathClassLoader;
@@ -207,6 +210,11 @@ public final class SystemServiceManager implements Dumpable {
* @throws RuntimeException if the service fails to start.
*/
@android.ravenwood.annotation.RavenwoodKeep
+ @UsesReflection(
+ @KeepTarget(
+ instanceOfClassConstantExclusive = SystemService.class,
+ methodName = "<init>",
+ methodParameterTypePatterns = {@TypePattern(constant = Context.class)}))
public <T extends SystemService> T startService(Class<T> serviceClass) {
try {
final String name = serviceClass.getName();
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 603a95c31e5b..1015ad9fe1da 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -880,6 +880,14 @@ public class AccountManagerService
packagesToVisibility = Collections.emptyMap();
accountRemovedReceivers = Collections.emptyList();
}
+ if (notify) {
+ Integer oldVisibility =
+ accounts.accountsDb.findAccountVisibility(account, packageName);
+ if (oldVisibility != null && oldVisibility == newVisibility) {
+ // Database will not be updated - skip LOGIN_ACCOUNTS_CHANGED broadcast.
+ notify = false;
+ }
+ }
if (!updateAccountVisibilityLocked(account, packageName, newVisibility, accounts)) {
return false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 458bd9473bab..ad15ea90c45c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -635,7 +635,8 @@ public class ActivityManagerService extends IActivityManager.Stub
static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
static final String EXTRA_BUGREPORT_TYPE = "android.intent.extra.BUGREPORT_TYPE";
static final String EXTRA_BUGREPORT_NONCE = "android.intent.extra.BUGREPORT_NONCE";
-
+ static final String EXTRA_EXTRA_ATTACHMENT_URI =
+ "android.intent.extra.EXTRA_ATTACHMENT_URI";
/**
* It is now required for apps to explicitly set either
* {@link android.content.Context#RECEIVER_EXPORTED} or
@@ -724,6 +725,9 @@ public class ActivityManagerService extends IActivityManager.Stub
// Whether we should use SCHED_FIFO for UI and RenderThreads.
final boolean mUseFifoUiScheduling;
+ /** Whether some specified important processes are allowed to use FIFO priority. */
+ boolean mAllowSpecifiedFifoScheduling = true;
+
@GuardedBy("this")
private final SparseArray<IUnsafeIntentStrictModeCallback>
mStrictModeCallbacks = new SparseArray<>();
@@ -1047,6 +1051,10 @@ public class ActivityManagerService extends IActivityManager.Stub
@GuardedBy("this")
final SparseArray<ImportanceToken> mImportantProcesses = new SparseArray<ImportanceToken>();
+ /** The processes that are allowed to use SCHED_FIFO prorioty. */
+ @GuardedBy("mProcLock")
+ final ArrayList<ProcessRecord> mSpecifiedFifoProcesses = new ArrayList<>();
+
/**
* List of records for processes that someone had tried to start before the
* system was ready. We don't start them at that point, but ensure they
@@ -7656,6 +7664,16 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
public void requestBugReportWithDescription(@Nullable String shareTitle,
@Nullable String shareDescription, int bugreportType, long nonce) {
+ requestBugReportWithDescription(shareTitle, shareDescription, bugreportType, nonce, null);
+ }
+
+ /**
+ * Takes a bugreport using bug report API ({@code BugreportManager}) which gets
+ * triggered by sending a broadcast to Shell. Optionally adds an extra attachment.
+ */
+ public void requestBugReportWithDescription(@Nullable String shareTitle,
+ @Nullable String shareDescription, int bugreportType, long nonce,
+ @Nullable Uri extraAttachment) {
String type = null;
switch (bugreportType) {
case BugreportParams.BUGREPORT_MODE_FULL:
@@ -7710,6 +7728,10 @@ public class ActivityManagerService extends IActivityManager.Stub
triggerShellBugreport.setPackage(SHELL_APP_PACKAGE);
triggerShellBugreport.putExtra(EXTRA_BUGREPORT_TYPE, bugreportType);
triggerShellBugreport.putExtra(EXTRA_BUGREPORT_NONCE, nonce);
+ if (extraAttachment != null) {
+ triggerShellBugreport.putExtra(EXTRA_EXTRA_ATTACHMENT_URI, extraAttachment);
+ triggerShellBugreport.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
if (shareTitle != null) {
@@ -7763,6 +7785,15 @@ public class ActivityManagerService extends IActivityManager.Stub
}
/**
+ * Takes an interactive bugreport with a progress notification. Also attaches given file uri.
+ */
+ @Override
+ public void requestBugReportWithExtraAttachment(@NonNull Uri extraAttachment) {
+ requestBugReportWithDescription(null, null, BugreportParams.BUGREPORT_MODE_INTERACTIVE, 0L,
+ extraAttachment);
+ }
+
+ /**
* Takes an interactive bugreport with a progress notification. Also, shows the given title and
* description on the final share notification
*/
@@ -8220,6 +8251,27 @@ public class ActivityManagerService extends IActivityManager.Stub
return false;
}
+ /**
+ * Switches the priority between SCHED_FIFO and SCHED_OTHER for the main thread and render
+ * thread of the given process.
+ */
+ @GuardedBy("mProcLock")
+ static void setFifoPriority(@NonNull ProcessRecord app, boolean enable) {
+ final int pid = app.getPid();
+ final int renderThreadTid = app.getRenderThreadTid();
+ if (enable) {
+ scheduleAsFifoPriority(pid, true /* suppressLogs */);
+ if (renderThreadTid != 0) {
+ scheduleAsFifoPriority(renderThreadTid, true /* suppressLogs */);
+ }
+ } else {
+ scheduleAsRegularPriority(pid, true /* suppressLogs */);
+ if (renderThreadTid != 0) {
+ scheduleAsRegularPriority(renderThreadTid, true /* suppressLogs */);
+ }
+ }
+ }
+
@Override
public void setRenderThread(int tid) {
synchronized (mProcLock) {
@@ -8245,7 +8297,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// promote to FIFO now
if (proc.mState.getCurrentSchedulingGroup() == ProcessList.SCHED_GROUP_TOP_APP) {
if (DEBUG_OOM_ADJ) Slog.d("UI_FIFO", "Promoting " + tid + "out of band");
- if (mUseFifoUiScheduling) {
+ if (proc.useFifoUiScheduling()) {
setThreadScheduler(proc.getRenderThreadTid(),
SCHED_FIFO | SCHED_RESET_ON_FORK, 1);
} else {
@@ -11282,6 +11334,9 @@ public class ActivityManagerService extends IActivityManager.Stub
if (mAlwaysFinishActivities) {
pw.println(" mAlwaysFinishActivities=" + mAlwaysFinishActivities);
}
+ if (mAllowSpecifiedFifoScheduling) {
+ pw.println(" mAllowSpecifiedFifoScheduling=true");
+ }
if (dumpAll) {
pw.println(" Total persistent processes: " + numPers);
pw.println(" mProcessesReady=" + mProcessesReady
@@ -17350,6 +17405,12 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
}
+
+ if (com.android.window.flags.Flags.fifoPriorityForMajorUiProcesses()) {
+ synchronized (mProcLock) {
+ adjustFifoProcessesIfNeeded(uid, !active /* allowFifo */);
+ }
+ }
}
final boolean isCameraActiveForUid(@UserIdInt int uid) {
@@ -17358,6 +17419,34 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ /**
+ * This is called when the given uid is using camera. If the uid has top process state, then
+ * cancel the FIFO priority of the high priority processes.
+ */
+ @VisibleForTesting
+ @GuardedBy("mProcLock")
+ void adjustFifoProcessesIfNeeded(int preemptiveUid, boolean allowSpecifiedFifo) {
+ if (allowSpecifiedFifo == mAllowSpecifiedFifoScheduling) {
+ return;
+ }
+ if (!allowSpecifiedFifo) {
+ final UidRecord uidRec = mProcessList.mActiveUids.get(preemptiveUid);
+ if (uidRec == null || uidRec.getCurProcState() > PROCESS_STATE_TOP) {
+ // To avoid frequent switching by background camera usages, e.g. face unlock,
+ // face detection (auto rotation), screen attention (keep screen on).
+ return;
+ }
+ }
+ mAllowSpecifiedFifoScheduling = allowSpecifiedFifo;
+ for (int i = mSpecifiedFifoProcesses.size() - 1; i >= 0; i--) {
+ final ProcessRecord proc = mSpecifiedFifoProcesses.get(i);
+ if (proc.mState.getSetSchedGroup() != ProcessList.SCHED_GROUP_TOP_APP) {
+ continue;
+ }
+ setFifoPriority(proc, allowSpecifiedFifo /* enable */);
+ }
+ }
+
@GuardedBy("this")
final void doStopUidLocked(int uid, final UidRecord uidRec) {
mServices.stopInBackgroundLocked(uid);
@@ -17872,9 +17961,35 @@ public class ActivityManagerService extends IActivityManager.Stub
mUserController.setStopUserOnSwitch(value);
}
+ /** @deprecated use {@link #stopUserWithCallback(int, IStopUserCallback)} instead */
+ @Deprecated
+ @Override
+ public int stopUser(final int userId,
+ boolean stopProfileRegardlessOfParent, final IStopUserCallback callback) {
+ return stopUserExceptCertainProfiles(userId, stopProfileRegardlessOfParent, callback);
+ }
+
+ /** Stops the given user. */
+ @Override
+ public int stopUserWithCallback(@UserIdInt int userId, @Nullable IStopUserCallback callback) {
+ return mUserController.stopUser(userId, /* allowDelayedLocking= */ false,
+ /* callback= */ callback, /* keyEvictedCallback= */ null);
+ }
+
+ /**
+ * Stops the given user.
+ *
+ * Usually, callers can just use @link{#stopUserWithCallback(int, IStopUserCallback)} instead.
+ *
+ * @param stopProfileRegardlessOfParent whether to stop the profile regardless of who its
+ * parent is, e.g. even if the parent is the current user;
+ * its value is irrelevant for non-profile users.
+ */
@Override
- public int stopUser(final int userId, boolean force, final IStopUserCallback callback) {
- return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ false,
+ public int stopUserExceptCertainProfiles(@UserIdInt int userId,
+ boolean stopProfileRegardlessOfParent, @Nullable IStopUserCallback callback) {
+ return mUserController.stopUser(userId,
+ stopProfileRegardlessOfParent, /* allowDelayedLocking= */ false,
/* callback= */ callback, /* keyEvictedCallback= */ null);
}
@@ -17883,11 +17998,9 @@ public class ActivityManagerService extends IActivityManager.Stub
* stopping only if {@code config_multiuserDelayUserDataLocking} overlay is set true.
*
* <p>When delayed locking is not enabled through the overlay, this call becomes the same
- * with {@link #stopUser(int, boolean, IStopUserCallback)} call.
+ * with {@link #stopUserWithCallback(int, IStopUserCallback)} call.
*
* @param userId User id to stop.
- * @param force Force stop the user even if the user is related with system user or current
- * user.
* @param callback Callback called when user has stopped.
*
* @return {@link ActivityManager#USER_OP_SUCCESS} when user is stopped successfully. Returns
@@ -17897,9 +18010,8 @@ public class ActivityManagerService extends IActivityManager.Stub
// TODO(b/302662311): Add javadoc changes corresponding to the user property that allows
// delayed locking behavior once the private space flag is finalized.
@Override
- public int stopUserWithDelayedLocking(final int userId, boolean force,
- final IStopUserCallback callback) {
- return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ true,
+ public int stopUserWithDelayedLocking(@UserIdInt int userId, IStopUserCallback callback) {
+ return mUserController.stopUser(userId, /* allowDelayedLocking= */ true,
/* callback= */ callback, /* keyEvictedCallback= */ null);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 755631cce728..e70722ca6579 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2560,7 +2560,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"shell_runStopUser-" + userId + "-[stopUser]");
try {
- int res = mInterface.stopUser(userId, force, callback);
+ int res = mInterface.stopUserExceptCertainProfiles(
+ userId, /* stopProfileRegardlessOfParent= */ force, callback);
if (res != ActivityManager.USER_OP_SUCCESS) {
String txt = "";
switch (res) {
@@ -4394,7 +4395,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" Stop execution of USER_ID, not allowing it to run any");
pw.println(" code until a later explicit start or switch to it.");
pw.println(" -w: wait for stop-user to complete.");
- pw.println(" -f: force stop even if there are related users that cannot be stopped.");
+ pw.println(" -f: force stop, even if user has an unstoppable parent.");
pw.println(" is-user-stopped <USER_ID>");
pw.println(" Returns whether <USER_ID> has been stopped or not.");
pw.println(" get-started-user-state <USER_ID>");
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 4f46ecdb9228..f98799dd3723 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -124,7 +124,9 @@ import com.android.server.power.stats.BatteryExternalStatsWorker;
import com.android.server.power.stats.BatteryStatsDumpHelperImpl;
import com.android.server.power.stats.BatteryStatsImpl;
import com.android.server.power.stats.BatteryUsageStatsProvider;
-import com.android.server.power.stats.CpuAggregatedPowerStatsProcessor;
+import com.android.server.power.stats.CpuPowerStatsProcessor;
+import com.android.server.power.stats.MobileRadioPowerStatsProcessor;
+import com.android.server.power.stats.PhoneCallPowerStatsProcessor;
import com.android.server.power.stats.PowerStatsAggregator;
import com.android.server.power.stats.PowerStatsExporter;
import com.android.server.power.stats.PowerStatsScheduler;
@@ -408,11 +410,18 @@ public final class BatteryStatsService extends IBatteryStats.Stub
com.android.internal.R.bool.config_batteryStatsResetOnUnplugAfterSignificantCharge);
final long powerStatsThrottlePeriodCpu = context.getResources().getInteger(
com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodCpu);
+ final long powerStatsThrottlePeriodMobileRadio = context.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodMobileRadio);
mBatteryStatsConfig =
new BatteryStatsImpl.BatteryStatsConfig.Builder()
.setResetOnUnplugHighBatteryLevel(resetOnUnplugHighBatteryLevel)
.setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge)
- .setPowerStatsThrottlePeriodCpu(powerStatsThrottlePeriodCpu)
+ .setPowerStatsThrottlePeriodMillis(
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ powerStatsThrottlePeriodCpu)
+ .setPowerStatsThrottlePeriodMillis(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ powerStatsThrottlePeriodMobileRadio)
.build();
mPowerStatsUidResolver = new PowerStatsUidResolver();
mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
@@ -470,7 +479,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub
AggregatedPowerStatsConfig.STATE_SCREEN,
AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
.setProcessor(
- new CpuAggregatedPowerStatsProcessor(mPowerProfile, mCpuScalingPolicies));
+ new CpuPowerStatsProcessor(mPowerProfile, mCpuScalingPolicies));
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+ .setProcessor(
+ new MobileRadioPowerStatsProcessor(mPowerProfile));
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_PHONE,
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+ .setProcessor(new PhoneCallPowerStatsProcessor());
return config;
}
@@ -494,8 +516,16 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
public void systemServicesReady() {
- mStats.setPowerStatsCollectorEnabled(Flags.streamlinedBatteryStats());
- mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(Flags.streamlinedBatteryStats());
+ mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_CPU,
+ Flags.streamlinedBatteryStats());
+ mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ Flags.streamlinedConnectivityBatteryStats());
+ mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+ BatteryConsumer.POWER_COMPONENT_CPU,
+ Flags.streamlinedBatteryStats());
+ mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ Flags.streamlinedConnectivityBatteryStats());
mWorker.systemServicesReady();
mStats.systemServicesReady(mContext);
mCpuWakeupStats.systemServicesReady();
@@ -536,7 +566,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
* Notifies BatteryStatsService that the system server is ready.
*/
public void onSystemReady() {
- mStats.onSystemReady();
+ mStats.onSystemReady(mContext);
mPowerStatsScheduler.start(Flags.streamlinedBatteryStats());
}
@@ -1591,19 +1621,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
mHandler.post(() -> {
- final boolean update;
synchronized (mStats) {
// Ignore if no power state change.
if (mLastPowerStateFromRadio == powerState) return;
mLastPowerStateFromRadio = powerState;
- update = mStats.noteMobileRadioPowerStateLocked(powerState, timestampNs, uid,
+ mStats.noteMobileRadioPowerStateLocked(powerState, timestampNs, uid,
elapsedRealtime, uptime);
}
-
- if (update) {
- mWorker.scheduleSync("modem-data", BatteryExternalStatsWorker.UPDATE_RADIO);
- }
});
}
FrameworkStatsLog.write_non_chained(
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 5a750c2ba6c8..ea7a21dd19cd 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -72,7 +72,6 @@ import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYB
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
import static android.media.audio.Flags.roForegroundAudioControl;
-import static android.os.Process.SCHED_OTHER;
import static android.os.Process.THREAD_GROUP_BACKGROUND;
import static android.os.Process.THREAD_GROUP_DEFAULT;
import static android.os.Process.THREAD_GROUP_RESTRICTED;
@@ -81,7 +80,6 @@ import static android.os.Process.THREAD_PRIORITY_DISPLAY;
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
import static android.os.Process.setProcessGroup;
import static android.os.Process.setThreadPriority;
-import static android.os.Process.setThreadScheduler;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
@@ -3315,22 +3313,10 @@ public class OomAdjuster {
// do nothing if we already switched to RT
if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
app.getWindowProcessController().onTopProcChanged();
- if (mService.mUseFifoUiScheduling) {
+ if (app.useFifoUiScheduling()) {
// Switch UI pipeline for app to SCHED_FIFO
state.setSavedPriority(Process.getThreadPriority(app.getPid()));
- mService.scheduleAsFifoPriority(app.getPid(), true);
- if (renderThreadTid != 0) {
- mService.scheduleAsFifoPriority(renderThreadTid,
- /* suppressLogs */true);
- if (DEBUG_OOM_ADJ) {
- Slog.d("UI_FIFO", "Set RenderThread (TID " +
- renderThreadTid + ") to FIFO");
- }
- } else {
- if (DEBUG_OOM_ADJ) {
- Slog.d("UI_FIFO", "Not setting RenderThread TID");
- }
- }
+ ActivityManagerService.setFifoPriority(app, true /* enable */);
} else {
// Boost priority for top app UI and render threads
setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
@@ -3347,22 +3333,10 @@ public class OomAdjuster {
} else if (oldSchedGroup == SCHED_GROUP_TOP_APP
&& curSchedGroup != SCHED_GROUP_TOP_APP) {
app.getWindowProcessController().onTopProcChanged();
- if (mService.mUseFifoUiScheduling) {
- try {
- // Reset UI pipeline to SCHED_OTHER
- setThreadScheduler(app.getPid(), SCHED_OTHER, 0);
- setThreadPriority(app.getPid(), state.getSavedPriority());
- if (renderThreadTid != 0) {
- setThreadScheduler(renderThreadTid,
- SCHED_OTHER, 0);
- }
- } catch (IllegalArgumentException e) {
- Slog.w(TAG,
- "Failed to set scheduling policy, thread does not exist:\n"
- + e);
- } catch (SecurityException e) {
- Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
- }
+ if (app.useFifoUiScheduling()) {
+ // Reset UI pipeline to SCHED_OTHER
+ ActivityManagerService.setFifoPriority(app, false /* enable */);
+ setThreadPriority(app.getPid(), state.getSavedPriority());
} else {
// Reset priority for top app UI and render threads
setThreadPriority(app.getPid(), 0);
@@ -3557,7 +3531,7 @@ public class OomAdjuster {
// {@link SCHED_GROUP_TOP_APP}. We don't check render thread because it
// is not ready when attaching.
app.getWindowProcessController().onTopProcChanged();
- if (mService.mUseFifoUiScheduling) {
+ if (app.useFifoUiScheduling()) {
mService.scheduleAsFifoPriority(app.getPid(), true);
} else {
setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b93908974a42..08165275757e 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -745,6 +745,9 @@ class ProcessRecord implements WindowProcessListener {
mOnewayThread = thread;
}
mWindowProcessController.setThread(thread);
+ if (mWindowProcessController.useFifoUiScheduling()) {
+ mService.mSpecifiedFifoProcesses.add(this);
+ }
}
@GuardedBy({"mService", "mProcLock"})
@@ -752,9 +755,19 @@ class ProcessRecord implements WindowProcessListener {
mThread = null;
mOnewayThread = null;
mWindowProcessController.setThread(null);
+ if (mWindowProcessController.useFifoUiScheduling()) {
+ mService.mSpecifiedFifoProcesses.remove(this);
+ }
mProfile.onProcessInactive(tracker);
}
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean useFifoUiScheduling() {
+ return mService.mUseFifoUiScheduling
+ || (mService.mAllowSpecifiedFifoScheduling
+ && mWindowProcessController.useFifoUiScheduling());
+ }
+
@GuardedBy("mService")
int getDyingPid() {
return mDyingPid;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 390dca30ac96..1f89ca70ce8d 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -145,6 +145,7 @@ public class SettingsToPropertiesMapper {
"core_experiments_team_internal",
"core_graphics",
"core_libraries",
+ "crumpet",
"dck_framework",
"devoptions_settings",
"game",
@@ -169,6 +170,7 @@ public class SettingsToPropertiesMapper {
"pixel_biometrics_face",
"pixel_bluetooth",
"pixel_connectivity_gps",
+ "pixel_continuity",
"pixel_sensors",
"pixel_system_sw_video",
"pixel_watch",
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 60a8b50feeab..dd4cee47bee9 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -246,6 +246,7 @@ class UserController implements Handler.Callback {
*
* <p>Note: Current and system user (and their related profiles) are never stopped when
* switching users. Due to that, the actual number of running users can exceed mMaxRunningUsers
+ // TODO(b/310249114): Strongly consider *not* exempting the SYSTEM user's profile.
*/
@GuardedBy("mLock")
private int mMaxRunningUsers;
@@ -578,7 +579,8 @@ class UserController implements Handler.Callback {
// from outside.
Slogf.i(TAG, "Too many running users (%d). Attempting to stop user %d",
currentlyRunningLru.size(), userId);
- if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true,
+ if (stopUsersLU(userId,
+ /* stopProfileRegardlessOfParent= */ false, /* allowDelayedLocking= */ true,
/* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
== USER_OP_SUCCESS) {
// Technically, stopUsersLU can remove more than one user when stopping a parent.
@@ -875,7 +877,7 @@ class UserController implements Handler.Callback {
Slogf.i(TAG, "Stopping pre-created user " + userInfo.toFullString());
// Pre-created user was started right after creation so services could properly
// intialize it; it should be stopped right away as it's not really a "real" user.
- stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false,
+ stopUser(userInfo.id, /* allowDelayedLocking= */ false,
/* stopUserCallback= */ null, /* keyEvictedCallback= */ null);
return;
}
@@ -930,7 +932,7 @@ class UserController implements Handler.Callback {
}
int restartUser(final int userId, @UserStartMode int userStartMode) {
- return stopUser(userId, /* force= */ true, /* allowDelayedLocking= */ false,
+ return stopUser(userId, /* allowDelayedLocking= */ false,
/* stopUserCallback= */ null, new KeyEvictedCallback() {
@Override
public void keyEvicted(@UserIdInt int userId) {
@@ -966,18 +968,24 @@ class UserController implements Handler.Callback {
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
synchronized (mLock) {
- return stopUsersLU(userId, /* force= */ true, /* allowDelayedLocking= */
- false, /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
+ return stopUsersLU(userId, /* allowDelayedLocking= */ false,
+ /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
== ActivityManager.USER_OP_SUCCESS;
}
}
- int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
+ int stopUser(final int userId, boolean allowDelayedLocking,
+ final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
+ return stopUser(userId, true, allowDelayedLocking, stopUserCallback, keyEvictedCallback);
+ }
+
+ int stopUser(final int userId,
+ final boolean stopProfileRegardlessOfParent, final boolean allowDelayedLocking,
final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("UserController"
- + (force ? "-force" : "")
+ + (stopProfileRegardlessOfParent ? "-stopProfileRegardlessOfParent" : "")
+ (allowDelayedLocking ? "-allowDelayedLocking" : "")
+ (stopUserCallback != null ? "-withStopUserCallback" : "")
+ "-" + userId + "-[stopUser]");
@@ -987,20 +995,32 @@ class UserController implements Handler.Callback {
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
synchronized (mLock) {
- return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
- keyEvictedCallback);
+ return stopUsersLU(userId, stopProfileRegardlessOfParent, allowDelayedLocking,
+ stopUserCallback, keyEvictedCallback);
}
} finally {
t.traceEnd();
}
}
+ /** Stops the user along with its profiles. */
+ @GuardedBy("mLock")
+ private int stopUsersLU(final int userId, boolean allowDelayedLocking,
+ final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
+ return stopUsersLU(userId, /* stopProfileRegardlessOfParent= */ true,
+ allowDelayedLocking, stopUserCallback, keyEvictedCallback);
+ }
+
/**
* Stops the user along with its profiles. The method calls
* {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
+ *
+ * @param stopProfileRegardlessOfParent whether to stop the profile regardless of who its
+ * parent is, e.g. even if the parent is the current user
*/
@GuardedBy("mLock")
- private int stopUsersLU(final int userId, boolean force, boolean allowDelayedLocking,
+ private int stopUsersLU(final int userId,
+ boolean stopProfileRegardlessOfParent, boolean allowDelayedLocking,
final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
if (userId == UserHandle.USER_SYSTEM) {
return USER_OP_ERROR_IS_SYSTEM;
@@ -1008,34 +1028,28 @@ class UserController implements Handler.Callback {
if (isCurrentUserLU(userId)) {
return USER_OP_IS_CURRENT;
}
- // TODO(b/324647580): Refactor the idea of "force" and clean up. In the meantime...
- final int parentId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
- if (parentId != UserInfo.NO_PROFILE_GROUP_ID && parentId != userId) {
- if ((UserHandle.USER_SYSTEM == parentId || isCurrentUserLU(parentId)) && !force) {
- return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
+ if (!stopProfileRegardlessOfParent) {
+ final int parentId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+ if (parentId != UserInfo.NO_PROFILE_GROUP_ID && parentId != userId) {
+ // TODO(b/310249114): Strongly consider *not* exempting the SYSTEM user's profile.
+ if ((UserHandle.USER_SYSTEM == parentId || isCurrentUserLU(parentId))) {
+ return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
+ }
}
}
- TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- int[] usersToStop = getUsersToStopLU(userId);
- // If one of related users is system or current, no related users should be stopped
+ final int[] usersToStop = getUsersToStopLU(userId);
+
+ // Final safety check: abort if one of the users we would plan to stop must not be stopped.
+ // This should be impossible in the current code, but just in case.
for (int relatedUserId : usersToStop) {
if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLU(relatedUserId)) {
- if (DEBUG_MU) {
- Slogf.i(TAG, "stopUsersLocked cannot stop related user " + relatedUserId);
- }
- // We still need to stop the requested user if it's a force stop.
- if (force) {
- Slogf.i(TAG,
- "Force stop user " + userId + ". Related users will not be stopped");
- t.traceBegin("stopSingleUserLU-force-" + userId + "-[stopUser]");
- stopSingleUserLU(userId, allowDelayedLocking, stopUserCallback,
- keyEvictedCallback);
- t.traceEnd();
- return USER_OP_SUCCESS;
- }
+ Slogf.e(TAG, "Cannot stop user %d because it is related to user %d. ",
+ userId, relatedUserId);
return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
}
}
+
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog();
if (DEBUG_MU) Slogf.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
for (int userIdToStop : usersToStop) {
t.traceBegin("stopSingleUserLU-" + userIdToStop + "-[stopUser]");
@@ -1304,8 +1318,8 @@ class UserController implements Handler.Callback {
mInjector.activityManagerOnUserStopped(userId);
// Clean up all state and processes associated with the user.
// Kill all the processes for the user.
- t.traceBegin("forceStopUser-" + userId + "-[stopUser]");
- forceStopUser(userId, "finish user");
+ t.traceBegin("stopPackagesOfStoppedUser-" + userId + "-[stopUser]");
+ stopPackagesOfStoppedUser(userId, "finish user");
t.traceEnd();
}
@@ -1516,8 +1530,8 @@ class UserController implements Handler.Callback {
return userIds.toArray();
}
- private void forceStopUser(@UserIdInt int userId, String reason) {
- if (DEBUG_MU) Slogf.i(TAG, "forceStopUser(%d): %s", userId, reason);
+ private void stopPackagesOfStoppedUser(@UserIdInt int userId, String reason) {
+ if (DEBUG_MU) Slogf.i(TAG, "stopPackagesOfStoppedUser(%d): %s", userId, reason);
mInjector.activityManagerForceStopPackage(userId, reason);
if (mInjector.getUserManager().isPreCreated(userId)) {
// Don't fire intent for precreated.
@@ -1566,8 +1580,7 @@ class UserController implements Handler.Callback {
// This is a user to be stopped.
Slogf.i(TAG, "Stopping background guest or ephemeral user " + oldUserId);
synchronized (mLock) {
- stopUsersLU(oldUserId, /* force= */ true, /* allowDelayedLocking= */ false,
- null, null);
+ stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null);
}
}
}
@@ -2259,8 +2272,7 @@ class UserController implements Handler.Callback {
// If running in background is disabled or mStopUserOnSwitch mode, stop the user.
if (hasRestriction || shouldStopUserOnSwitch()) {
Slogf.i(TAG, "Stopping user %d and its profiles on user switch", oldUserId);
- stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ false,
- null, null);
+ stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null);
return;
}
}
@@ -2275,7 +2287,8 @@ class UserController implements Handler.Callback {
Slogf.i(TAG, "Stopping profile %d on user switch", profileUserId);
synchronized (mLock) {
stopUsersLU(profileUserId,
- /* force= */ true, /* allowDelayedLocking= */ false, null, null);
+ /* stopProfileRegardlessOfParent= */ false,
+ /* allowDelayedLocking= */ false, null, null);
}
}
}
diff --git a/services/core/java/com/android/server/app/flags.aconfig b/services/core/java/com/android/server/app/flags.aconfig
index 54e45716a232..0673013bdc44 100644
--- a/services/core/java/com/android/server/app/flags.aconfig
+++ b/services/core/java/com/android/server/app/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.server.app"
-container: "system"
flag {
name: "game_default_frame_rate"
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index debd9d0f0c83..be39778372ca 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1364,6 +1364,9 @@ public class AppOpsService extends IAppOpsService.Stub {
@GuardedBy("this")
private void packageRemovedLocked(int uid, String packageName) {
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
+ mHistoricalRegistry, uid, packageName));
+
UidState uidState = mUidStates.get(uid);
if (uidState == null) {
return;
@@ -1398,9 +1401,6 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
}
-
- mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
- mHistoricalRegistry, uid, packageName));
}
public void uidRemoved(int uid) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5ba0af402e4d..0e22ef1eb741 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4674,6 +4674,12 @@ public class AudioService extends IAudioService.Stub
int streamTypeAlias = mStreamVolumeAlias[streamType];
VolumeStreamState streamState = mStreamStates[streamTypeAlias];
+ if ((streamType == AudioManager.STREAM_VOICE_CALL)
+ && isInCommunication() && mDeviceBroker.isBluetoothScoActive()) {
+ Log.i(TAG, "setStreamVolume for STREAM_VOICE_CALL, switching to STREAM_BLUETOOTH_SCO");
+ streamType = AudioManager.STREAM_BLUETOOTH_SCO;
+ }
+
final int device = (ada == null)
? getDeviceForStream(streamType)
: ada.getInternalType();
@@ -5464,19 +5470,19 @@ public class AudioService extends IAudioService.Stub
/** @see AudioManager#setMicrophoneMuteFromSwitch(boolean) */
public void setMicrophoneMuteFromSwitch(boolean on) {
- int userId = Binder.getCallingUid();
- if (userId != android.os.Process.SYSTEM_UID) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != android.os.Process.SYSTEM_UID) {
Log.e(TAG, "setMicrophoneMuteFromSwitch() called from non system user!");
return;
}
mMicMuteFromSwitch = on;
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_MIC)
- .setUid(userId)
+ .setUid(callingUid)
.set(MediaMetrics.Property.EVENT, "setMicrophoneMuteFromSwitch")
.set(MediaMetrics.Property.REQUEST, on
? MediaMetrics.Value.MUTE : MediaMetrics.Value.UNMUTE)
.record();
- setMicrophoneMuteNoCallerCheck(userId);
+ setMicrophoneMuteNoCallerCheck(UserHandle.getCallingUserId());
}
private void setMicMuteFromSwitchInput() {
@@ -5507,9 +5513,10 @@ public class AudioService extends IAudioService.Stub
if (DEBUG_VOL) {
Log.d(TAG, String.format("Mic mute %b, user=%d", muted, userId));
}
- // only mute for the current user
- if (getCurrentUserId() == userId || userId == android.os.Process.SYSTEM_UID) {
+ // only mute for the current user or for the system user.
+ if (getCurrentUserId() == userId || userId == UserHandle.USER_SYSTEM) {
final boolean currentMute = mAudioSystem.isMicrophoneMuted();
+ int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
final int ret = mAudioSystem.muteMicrophone(muted);
@@ -5522,7 +5529,7 @@ public class AudioService extends IAudioService.Stub
}
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_MIC)
- .setUid(userId)
+ .setUid(callingUid)
.set(MediaMetrics.Property.EVENT, "setMicrophoneMuteNoCallerCheck")
.set(MediaMetrics.Property.MUTE, mMicMuteFromSystemCached
? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 5b9469bc5610..1ea72d7da2fc 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -34,10 +34,11 @@ import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
-import com.google.android.collect.Sets;
import com.android.server.backup.Flags;
+import com.google.android.collect.Sets;
+
import java.io.File;
import java.io.IOException;
import java.util.Set;
@@ -64,6 +65,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
private static final String APP_LOCALES_HELPER = "app_locales";
private static final String APP_GENDER_HELPER = "app_gender";
private static final String COMPANION_HELPER = "companion";
+ private static final String SYSTEM_GENDER_HELPER = "system_gender";
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
@@ -99,7 +101,9 @@ public class SystemBackupAgent extends BackupAgentHelper {
NOTIFICATION_HELPER,
SYNC_SETTINGS_HELPER,
APP_LOCALES_HELPER,
- COMPANION_HELPER);
+ COMPANION_HELPER,
+ APP_GENDER_HELPER,
+ SYSTEM_GENDER_HELPER);
/** Helpers that are enabled for full, non-system users. */
private static final Set<String> sEligibleHelpersForNonSystemUser =
@@ -139,6 +143,8 @@ public class SystemBackupAgent extends BackupAgentHelper {
addHelperIfEligibleForUser(APP_GENDER_HELPER,
new AppGrammaticalGenderBackupHelper(mUserId));
addHelperIfEligibleForUser(COMPANION_HELPER, new CompanionBackupHelper(mUserId));
+ addHelperIfEligibleForUser(SYSTEM_GENDER_HELPER,
+ new SystemGrammaticalGenderBackupHelper(mUserId));
}
@Override
diff --git a/services/core/java/com/android/server/backup/SystemGrammaticalGenderBackupHelper.java b/services/core/java/com/android/server/backup/SystemGrammaticalGenderBackupHelper.java
new file mode 100644
index 000000000000..c0d398e02c6f
--- /dev/null
+++ b/services/core/java/com/android/server/backup/SystemGrammaticalGenderBackupHelper.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import static android.app.backup.BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+
+import android.annotation.UserIdInt;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BlobBackupHelper;
+import android.os.ParcelFileDescriptor;
+
+import com.android.server.LocalServices;
+import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal;
+
+public class SystemGrammaticalGenderBackupHelper extends BlobBackupHelper {
+ private static final int BLOB_VERSION = 1;
+ private static final String KEY_SYSTEM_GENDER = "system_gender";
+
+ private final @UserIdInt int mUserId;
+ private final GrammaticalInflectionManagerInternal mGrammarInflectionManagerInternal;
+
+ public SystemGrammaticalGenderBackupHelper(int userId) {
+ super(BLOB_VERSION, KEY_SYSTEM_GENDER);
+ mUserId = userId;
+ mGrammarInflectionManagerInternal = LocalServices.getService(
+ GrammaticalInflectionManagerInternal.class);
+ }
+
+ @Override
+ public void performBackup(ParcelFileDescriptor oldStateFd, BackupDataOutput data,
+ ParcelFileDescriptor newStateFd) {
+ // Only backup the gender data if e2e encryption is present
+ if ((data.getTransportFlags() & FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED) == 0) {
+ return;
+ }
+
+ super.performBackup(oldStateFd, data, newStateFd);
+ }
+
+ @Override
+ protected byte[] getBackupPayload(String key) {
+ return KEY_SYSTEM_GENDER.equals(key) && mGrammarInflectionManagerInternal != null
+ ? mGrammarInflectionManagerInternal.getSystemBackupPayload(mUserId) : null;
+ }
+
+ @Override
+ protected void applyRestoredPayload(String key, byte[] payload) {
+ if (KEY_SYSTEM_GENDER.equals(key) && mGrammarInflectionManagerInternal != null) {
+ mGrammarInflectionManagerInternal.applyRestoredSystemPayload(payload, mUserId);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index f51b62d77ab9..4af30a923b4c 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -89,11 +89,23 @@ public class Utils {
return true;
}
- /** If virtualized biometrics are supported (requires debug build). */
- public static boolean isVirtualEnabled(@NonNull Context context) {
+ /** If virtualized fingerprint sensor is supported. */
+ public static boolean isFingerprintVirtualEnabled(@NonNull Context context) {
return Build.isDebuggable()
- && Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
+ && (Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED, 0,
+ UserHandle.USER_CURRENT) == 1
+ || Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 0, UserHandle.USER_CURRENT) == 1);
+ }
+
+ /** If virtualized face sensor is supported. */
+ public static boolean isFaceVirtualEnabled(@NonNull Context context) {
+ return Build.isDebuggable()
+ && (Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.BIOMETRIC_FACE_VIRTUAL_ENABLED, 0, UserHandle.USER_CURRENT) == 1
+ || Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 0, UserHandle.USER_CURRENT) == 1);
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 1037124d7048..a946af88da18 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -748,7 +748,7 @@ public class FaceService extends SystemService {
final String virtualInstance = "virtual";
final boolean isVirtualHalPresent =
faceSensorConfigurations.doesInstanceExist(virtualInstance);
- if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
+ if (Flags.faceVhalFeature() && Utils.isFaceVirtualEnabled(getContext())) {
if (isVirtualHalPresent) {
return new Pair<>(virtualInstance,
faceSensorConfigurations.getSensorPropForInstance(virtualInstance));
@@ -786,7 +786,7 @@ public class FaceService extends SystemService {
}
final int virtualAt = aidlInstances.indexOf("virtual");
- if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
+ if (Flags.faceVhalFeature() && Utils.isFaceVirtualEnabled(getContext())) {
if (virtualAt != -1) {
//only virtual instance should be returned
Slog.i(TAG, "virtual hal is used");
@@ -928,7 +928,7 @@ public class FaceService extends SystemService {
void syncEnrollmentsNow() {
Utils.checkPermissionOrShell(getContext(), MANAGE_FACE);
- if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
+ if (Flags.faceVhalFeature() && Utils.isFaceVirtualEnabled(getContext())) {
Slog.i(TAG, "Sync virtual enrollments");
final int userId = ActivityManager.getCurrentUser();
for (ServiceProvider provider : mRegistry.getProviders()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 2dc03ed94b6c..d762914a90c7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -1136,7 +1136,7 @@ public class FingerprintService extends SystemService {
final String virtualInstance = "virtual";
final boolean isVirtualHalPresent =
fingerprintSensorConfigurations.doesInstanceExist(virtualInstance);
- if (Utils.isVirtualEnabled(getContext())) {
+ if (Utils.isFingerprintVirtualEnabled(getContext())) {
if (isVirtualHalPresent) {
return new Pair<>(virtualInstance,
fingerprintSensorConfigurations.getSensorPropForInstance(virtualInstance));
@@ -1169,7 +1169,7 @@ public class FingerprintService extends SystemService {
}
final int virtualAt = aidlInstances.indexOf("virtual");
- if (Utils.isVirtualEnabled(getContext())) {
+ if (Utils.isFingerprintVirtualEnabled(getContext())) {
if (virtualAt != -1) {
//only virtual instance should be returned
Slog.i(TAG, "virtual hal is used");
@@ -1295,7 +1295,7 @@ public class FingerprintService extends SystemService {
void syncEnrollmentsNow() {
Utils.checkPermissionOrShell(getContext(), MANAGE_FINGERPRINT);
- if (Utils.isVirtualEnabled(getContext())) {
+ if (Utils.isFingerprintVirtualEnabled(getContext())) {
Slog.i(TAG, "Sync virtual enrollments");
final int userId = ActivityManager.getCurrentUser();
final CountDownLatch latch = new CountDownLatch(mRegistry.getProviders().size());
@@ -1324,7 +1324,7 @@ public class FingerprintService extends SystemService {
}
void simulateVhalFingerDown() {
- if (Utils.isVirtualEnabled(getContext())) {
+ if (Utils.isFingerprintVirtualEnabled(getContext())) {
Slog.i(TAG, "Simulate virtual HAL finger down event");
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider != null) {
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
index 16514fa813dc..e6de14bcf9aa 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
@@ -29,7 +29,6 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.util.IndentingPrintWriter;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -122,7 +121,8 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
+ " without permission " + Manifest.permission.DUMP);
return;
}
- IndentingPrintWriter radioPrintWriter = new IndentingPrintWriter(printWriter);
+ android.util.IndentingPrintWriter radioPrintWriter =
+ new android.util.IndentingPrintWriter(printWriter);
radioPrintWriter.printf("BroadcastRadioService\n");
radioPrintWriter.increaseIndent();
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
index ab083429a200..93fb7b2525fb 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
@@ -26,7 +26,6 @@ import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
import android.os.Binder;
import android.os.RemoteException;
-import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
@@ -139,7 +138,7 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
+ " without permission " + Manifest.permission.DUMP);
return;
}
- IndentingPrintWriter radioPw = new IndentingPrintWriter(pw);
+ android.util.IndentingPrintWriter radioPw = new android.util.IndentingPrintWriter(pw);
radioPw.printf("BroadcastRadioService\n");
radioPw.increaseIndent();
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioLogger.java b/services/core/java/com/android/server/broadcastradio/RadioEventLogger.java
index cca351bc0d73..2c8f499c619b 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioLogger.java
+++ b/services/core/java/com/android/server/broadcastradio/RadioEventLogger.java
@@ -14,31 +14,35 @@
* limitations under the License.
*/
-package com.android.server.broadcastradio.aidl;
+package com.android.server.broadcastradio;
import android.text.TextUtils;
-import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import android.util.Log;
import com.android.server.utils.Slogf;
/**
- * Event logger to log and dump events of radio module and tuner session
- * for AIDL broadcast radio HAL
+ * Event logger to log and dump events of broadcast radio service client for HIDL and AIDL
+ * broadcast HAL.
*/
-final class RadioLogger {
+public final class RadioEventLogger {
private final String mTag;
private final boolean mDebug;
private final LocalLog mEventLogger;
- RadioLogger(String tag, int loggerQueueSize) {
+ public RadioEventLogger(String tag, int loggerQueueSize) {
mTag = tag;
mDebug = Log.isLoggable(mTag, Log.DEBUG);
mEventLogger = new LocalLog(loggerQueueSize);
}
- void logRadioEvent(String logFormat, Object... args) {
+ /**
+ * Log broadcast radio service event
+ * @param logFormat String format of log message
+ * @param args Arguments of log message
+ */
+ public void logRadioEvent(String logFormat, Object... args) {
String log = TextUtils.formatSimple(logFormat, args);
mEventLogger.log(log);
if (mDebug) {
@@ -46,7 +50,11 @@ final class RadioLogger {
}
}
- void dump(IndentingPrintWriter pw) {
+ /**
+ * Dump broadcast radio service event
+ * @param pw Indenting print writer for dump
+ */
+ public void dump(android.util.IndentingPrintWriter pw) {
mEventLogger.dump(pw);
}
}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/AnnouncementAggregator.java b/services/core/java/com/android/server/broadcastradio/aidl/AnnouncementAggregator.java
index b618aa3d65dc..9654a93d2036 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/AnnouncementAggregator.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/AnnouncementAggregator.java
@@ -22,7 +22,6 @@ import android.hardware.radio.IAnnouncementListener;
import android.hardware.radio.ICloseHandle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.IndentingPrintWriter;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -94,7 +93,7 @@ public final class AnnouncementAggregator extends ICloseHandle.Stub {
if (mCloseHandle != null) mCloseHandle.close();
}
- public void dumpInfo(IndentingPrintWriter pw) {
+ public void dumpInfo(android.util.IndentingPrintWriter pw) {
pw.printf("ModuleWatcher:\n");
pw.increaseIndent();
@@ -192,7 +191,8 @@ public final class AnnouncementAggregator extends ICloseHandle.Stub {
@Override
protected void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
- IndentingPrintWriter announcementPrintWriter = new IndentingPrintWriter(printWriter);
+ android.util.IndentingPrintWriter announcementPrintWriter =
+ new android.util.IndentingPrintWriter(printWriter);
announcementPrintWriter.printf("AnnouncementAggregator\n");
announcementPrintWriter.increaseIndent();
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index 086f3aa8ad65..1c421614599d 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -29,7 +29,6 @@ import android.os.IServiceCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArrayMap;
-import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.SparseArray;
@@ -261,7 +260,7 @@ public final class BroadcastRadioServiceImpl {
*
* @param pw The file to which {@link BroadcastRadioServiceImpl} state is dumped.
*/
- public void dumpInfo(IndentingPrintWriter pw) {
+ public void dumpInfo(android.util.IndentingPrintWriter pw) {
synchronized (mLock) {
pw.printf("Next module id available: %d\n", mNextModuleId);
pw.printf("ServiceName to module id map:\n");
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index cd865105c48e..0cac35641ed0 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -38,10 +38,10 @@ import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.broadcastradio.RadioEventLogger;
import com.android.server.broadcastradio.RadioServiceUserController;
import com.android.server.utils.Slogf;
@@ -59,7 +59,7 @@ final class RadioModule {
private final Object mLock = new Object();
private final Handler mHandler;
- private final RadioLogger mLogger;
+ private final RadioEventLogger mLogger;
private final RadioManager.ModuleProperties mProperties;
/**
@@ -197,7 +197,7 @@ final class RadioModule {
mProperties = Objects.requireNonNull(properties, "properties cannot be null");
mService = Objects.requireNonNull(service, "service cannot be null");
mHandler = new Handler(Looper.getMainLooper());
- mLogger = new RadioLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE);
+ mLogger = new RadioEventLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE);
}
@Nullable
@@ -524,7 +524,7 @@ final class RadioModule {
return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
}
- void dumpInfo(IndentingPrintWriter pw) {
+ void dumpInfo(android.util.IndentingPrintWriter pw) {
pw.printf("RadioModule\n");
pw.increaseIndent();
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
index 4ed36ec9878c..925f149b12cf 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
@@ -29,9 +29,9 @@ import android.os.Binder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.broadcastradio.RadioEventLogger;
import com.android.server.broadcastradio.RadioServiceUserController;
import com.android.server.utils.Slogf;
@@ -45,7 +45,7 @@ final class TunerSession extends ITuner.Stub {
private final Object mLock = new Object();
- private final RadioLogger mLogger;
+ private final RadioEventLogger mLogger;
private final RadioModule mModule;
final int mUserId;
final android.hardware.radio.ITunerCallback mCallback;
@@ -70,7 +70,7 @@ final class TunerSession extends ITuner.Stub {
mUserId = Binder.getCallingUserHandle().getIdentifier();
mCallback = Objects.requireNonNull(callback, "callback cannot be null");
mUid = Binder.getCallingUid();
- mLogger = new RadioLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
+ mLogger = new RadioEventLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
}
@Override
@@ -434,7 +434,7 @@ final class TunerSession extends ITuner.Stub {
}
}
- void dumpInfo(IndentingPrintWriter pw) {
+ void dumpInfo(android.util.IndentingPrintWriter pw) {
pw.printf("TunerSession\n");
pw.increaseIndent();
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 3198842c1ff3..e1650c227266 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -30,7 +30,6 @@ import android.hidl.manager.V1_0.IServiceNotification;
import android.os.IHwBinder.DeathRecipient;
import android.os.RemoteException;
import android.util.ArrayMap;
-import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -222,7 +221,7 @@ public final class BroadcastRadioService {
*
* @param pw The file to which BroadcastRadioService state is dumped.
*/
- public void dumpInfo(IndentingPrintWriter pw) {
+ public void dumpInfo(android.util.IndentingPrintWriter pw) {
synchronized (mLock) {
pw.printf("Next module id available: %d\n", mNextModuleId);
pw.printf("ServiceName to module id map:\n");
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java
deleted file mode 100644
index b8d12280ac05..000000000000
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * Copyright (C) 2022 The Android Open 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.broadcastradio.hal2;
-
-import android.util.IndentingPrintWriter;
-import android.util.LocalLog;
-import android.util.Log;
-
-import com.android.server.utils.Slogf;
-
-final class RadioEventLogger {
- private final String mTag;
- private final LocalLog mEventLogger;
-
- RadioEventLogger(String tag, int loggerQueueSize) {
- mTag = tag;
- mEventLogger = new LocalLog(loggerQueueSize);
- }
-
- @SuppressWarnings("AnnotateFormatMethod")
- void logRadioEvent(String logFormat, Object... args) {
- String log = String.format(logFormat, args);
- mEventLogger.log(log);
- if (Log.isLoggable(mTag, Log.DEBUG)) {
- Slogf.d(mTag, log);
- }
- }
-
- void dump(IndentingPrintWriter pw) {
- mEventLogger.dump(pw);
- }
-}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 0e11df8282a7..7269f24fc4b1 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -40,11 +40,11 @@ import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
import android.util.MutableInt;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.broadcastradio.RadioEventLogger;
import com.android.server.broadcastradio.RadioServiceUserController;
import com.android.server.utils.Slogf;
@@ -453,7 +453,7 @@ final class RadioModule {
return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
}
- void dumpInfo(IndentingPrintWriter pw) {
+ void dumpInfo(android.util.IndentingPrintWriter pw) {
pw.printf("RadioModule\n");
pw.increaseIndent();
pw.printf("BroadcastRadioService: %s\n", mService);
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 6d435e38117f..b1b5d3488a5b 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -31,11 +31,11 @@ import android.os.Binder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
import android.util.MutableBoolean;
import android.util.MutableInt;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.broadcastradio.RadioEventLogger;
import com.android.server.broadcastradio.RadioServiceUserController;
import com.android.server.utils.Slogf;
@@ -389,7 +389,7 @@ final class TunerSession extends ITuner.Stub {
}
}
- void dumpInfo(IndentingPrintWriter pw) {
+ void dumpInfo(android.util.IndentingPrintWriter pw) {
pw.printf("TunerSession\n");
pw.increaseIndent();
pw.printf("HIDL HAL Session: %s\n", mHwSession);
diff --git a/services/core/java/com/android/server/connectivity/Android.bp b/services/core/java/com/android/server/connectivity/Android.bp
new file mode 100644
index 000000000000..a374ec2cea9a
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+ name: "connectivity_flags",
+ package: "com.android.server.connectivity",
+ srcs: ["flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "connectivity_flags_lib",
+ aconfig_declarations: "connectivity_flags",
+}
diff --git a/services/core/java/com/android/server/connectivity/flags.aconfig b/services/core/java/com/android/server/connectivity/flags.aconfig
new file mode 100644
index 000000000000..32593d4bcdaa
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.connectivity"
+
+flag {
+ name: "replace_vpn_profile_store"
+ namespace: "android_core_networking"
+ description: "This flag controls the usage of VpnBlobStore to replace LegacyVpnProfileStore."
+ bug: "307903113"
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 06dc7f54dd8b..9c020a7d4fda 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -425,7 +425,7 @@ public class AutomaticBrightnessController {
return mScreenAutoBrightness;
}
- float getRawAutomaticScreenBrightness() {
+ public float getRawAutomaticScreenBrightness() {
return mRawScreenAutoBrightness;
}
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index d50a43aa93d1..e3e3be2e5188 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -18,6 +18,7 @@ package com.android.server.display;
import android.text.TextUtils;
+import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.BrightnessReason;
import java.util.Objects;
@@ -43,6 +44,8 @@ public final class DisplayBrightnessState {
private final float mCustomAnimationRate;
+ private final BrightnessEvent mBrightnessEvent;
+
private DisplayBrightnessState(Builder builder) {
mBrightness = builder.getBrightness();
mSdrBrightness = builder.getSdrBrightness();
@@ -54,6 +57,7 @@ public final class DisplayBrightnessState {
mMinBrightness = builder.getMinBrightness();
mCustomAnimationRate = builder.getCustomAnimationRate();
mShouldUpdateScreenBrightnessSetting = builder.shouldUpdateScreenBrightnessSetting();
+ mBrightnessEvent = builder.getBrightnessEvent();
}
/**
@@ -127,6 +131,13 @@ public final class DisplayBrightnessState {
return mShouldUpdateScreenBrightnessSetting;
}
+ /**
+ * @return The BrightnessEvent object
+ */
+ public BrightnessEvent getBrightnessEvent() {
+ return mBrightnessEvent;
+ }
+
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -144,6 +155,8 @@ public final class DisplayBrightnessState {
stringBuilder.append("\n customAnimationRate:").append(mCustomAnimationRate);
stringBuilder.append("\n shouldUpdateScreenBrightnessSetting:")
.append(mShouldUpdateScreenBrightnessSetting);
+ stringBuilder.append("\n mBrightnessEvent:")
+ .append(Objects.toString(mBrightnessEvent, "null"));
return stringBuilder.toString();
}
@@ -173,7 +186,8 @@ public final class DisplayBrightnessState {
&& mMinBrightness == otherState.getMinBrightness()
&& mCustomAnimationRate == otherState.getCustomAnimationRate()
&& mShouldUpdateScreenBrightnessSetting
- == otherState.shouldUpdateScreenBrightnessSetting();
+ == otherState.shouldUpdateScreenBrightnessSetting()
+ && Objects.equals(mBrightnessEvent, otherState.getBrightnessEvent());
}
@Override
@@ -181,7 +195,7 @@ public final class DisplayBrightnessState {
return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mMinBrightness,
mCustomAnimationRate,
- mShouldUpdateScreenBrightnessSetting);
+ mShouldUpdateScreenBrightnessSetting, mBrightnessEvent);
}
/**
@@ -206,6 +220,8 @@ public final class DisplayBrightnessState {
private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
private boolean mShouldUpdateScreenBrightnessSetting;
+ private BrightnessEvent mBrightnessEvent;
+
/**
* Create a builder starting with the values from the specified {@link
* DisplayBrightnessState}.
@@ -225,6 +241,7 @@ public final class DisplayBrightnessState {
builder.setCustomAnimationRate(state.getCustomAnimationRate());
builder.setShouldUpdateScreenBrightnessSetting(
state.shouldUpdateScreenBrightnessSetting());
+ builder.setBrightnessEvent(state.getBrightnessEvent());
return builder;
}
@@ -400,5 +417,22 @@ public final class DisplayBrightnessState {
public DisplayBrightnessState build() {
return new DisplayBrightnessState(this);
}
+
+
+ /**
+ * This is used to get the BrightnessEvent object from its builder
+ */
+ public BrightnessEvent getBrightnessEvent() {
+ return mBrightnessEvent;
+ }
+
+
+ /**
+ * This is used to set the BrightnessEvent object
+ */
+ public Builder setBrightnessEvent(BrightnessEvent brightnessEvent) {
+ mBrightnessEvent = brightnessEvent;
+ return this;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 85a231fafb0a..1c169a055078 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -585,7 +585,7 @@ import javax.xml.datatype.DatatypeConfigurationException;
* </point>
* </luxThresholds>
* </idleScreenRefreshRateTimeout>
- *
+ * <supportsVrr>true</supportsVrr>
*
* </displayConfiguration>
* }
@@ -872,6 +872,8 @@ public class DisplayDeviceConfig {
*/
private float mBrightnessCapForWearBedtimeMode;
+ private boolean mVrrSupportEnabled;
+
private final DisplayManagerFlags mFlags;
@VisibleForTesting
@@ -1606,6 +1608,13 @@ public class DisplayDeviceConfig {
return mBrightnessCapForWearBedtimeMode;
}
+ /**
+ * @return true if display supports dvrr
+ */
+ public boolean isVrrSupportEnabled() {
+ return mVrrSupportEnabled;
+ }
+
@Override
public String toString() {
return "DisplayDeviceConfig{"
@@ -1705,6 +1714,8 @@ public class DisplayDeviceConfig {
+ "\n"
+ "mEvenDimmerBrightnessData:" + (mEvenDimmerBrightnessData != null
? mEvenDimmerBrightnessData.toString() : "null")
+ + "\n"
+ + "mVrrSupported= " + mVrrSupportEnabled + "\n"
+ "}";
}
@@ -1779,6 +1790,7 @@ public class DisplayDeviceConfig {
mHdrBrightnessData = HdrBrightnessData.loadConfig(config);
loadBrightnessCapForWearBedtimeMode(config);
loadIdleScreenRefreshRateTimeoutConfigs(config);
+ mVrrSupportEnabled = config.getSupportsVrr();
} else {
Slog.w(TAG, "DisplayDeviceConfig file is null");
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 31092f27c838..8f1277bfe507 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2764,6 +2764,7 @@ public final class DisplayManagerService extends SystemService {
+ requestedRefreshRate + " on Display: " + displayId);
}
}
+
mDisplayModeDirector.getAppRequestObserver().setAppRequest(
displayId, requestedModeId, requestedMinRefreshRate, requestedMaxRefreshRate);
@@ -4939,6 +4940,18 @@ public final class DisplayManagerService extends SystemService {
}
@Override
+ public boolean isVrrSupportEnabled(int displayId) {
+ DisplayDevice device;
+ synchronized (mSyncRoot) {
+ device = getDeviceForDisplayLocked(displayId);
+ }
+ if (device == null) {
+ return false;
+ }
+ return device.getDisplayDeviceConfig().isVrrSupportEnabled();
+ }
+
+ @Override
public void setWindowManagerMirroring(int displayId, boolean isMirroring) {
synchronized (mSyncRoot) {
final DisplayDevice device = getDeviceForDisplayLocked(displayId);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 404c3ad3a51b..cfdb75f418de 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -83,7 +83,7 @@ import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.brightness.DisplayBrightnessController;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
-import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
import com.android.server.display.config.HysteresisLevels;
@@ -440,7 +440,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// Responsible for evaluating and tracking the automatic brightness relevant states.
// Todo: This is a temporary workaround. Ideally DPC2 should never talk to the strategies
- private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+ private final AutomaticBrightnessStrategy2 mAutomaticBrightnessStrategy;
// A record of state for skipping brightness ramps.
private int mSkipRampState = RAMP_STATE_SKIP_NONE;
@@ -1337,9 +1337,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
}
- final boolean userSetBrightnessChanged = mDisplayBrightnessController
- .updateUserSetScreenBrightness();
-
DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController
.updateBrightness(mPowerRequest, state);
float brightnessState = displayBrightnessState.getBrightness();
@@ -1348,7 +1345,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
boolean slowChange = displayBrightnessState.isSlowChange();
// custom transition duration
float customAnimationRate = displayBrightnessState.getCustomAnimationRate();
-
+ final boolean userSetBrightnessChanged =
+ mDisplayBrightnessController.getIsUserSetScreenBrightnessUpdated();
+ if (displayBrightnessState.getBrightnessEvent() != null) {
+ mTempBrightnessEvent.copyFrom(displayBrightnessState.getBrightnessEvent());
+ }
// Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
// doesn't yet have a valid lux value to use with auto-brightness.
if (mScreenOffBrightnessSensorController != null) {
@@ -1364,11 +1365,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// request changes.
final boolean wasShortTermModelActive =
mAutomaticBrightnessStrategy.isShortTermModelActive();
- mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
- mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
- mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
- mDisplayBrightnessController.getLastUserSetScreenBrightness(),
- userSetBrightnessChanged);
+ if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
+ mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
+ mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
+ mDisplayBrightnessController.getLastUserSetScreenBrightness(),
+ userSetBrightnessChanged);
+ }
// If the brightness is already set then it's been overridden by something other than the
// user, or is a temporary adjustment.
@@ -1390,9 +1393,22 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
// Apply auto-brightness.
int brightnessAdjustmentFlags = 0;
+ // All the conditions inside this if block will be moved to AutomaticBrightnessStrategy
+ if (mFlags.isRefactorDisplayPowerControllerEnabled()
+ && displayBrightnessState.getBrightnessReason().getReason()
+ == BrightnessReason.REASON_AUTOMATIC) {
+ brightnessAdjustmentFlags =
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
+ updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+ }
+ setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ }
// AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
- if (Float.isNaN(brightnessState)
- || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD) {
+ if (!mFlags.isRefactorDisplayPowerControllerEnabled() && (Float.isNaN(brightnessState)
+ || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD)) {
if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
mTempBrightnessEvent);
@@ -1422,8 +1438,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
} else {
// Any non-auto-brightness values such as override or temporary should still be subject
- // to clamping so that they don't go beyond the current max as specified by HBM
- // Controller.
+ // to clamping so that they don't go beyond the current max as specified by Brightness
+ // Range Controller.
brightnessState = clampScreenBrightness(brightnessState);
mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
}
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index a12d2481330b..b24caf4ced76 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -222,7 +222,7 @@ class ExternalDisplayPolicy {
} else {
// As external display is enabled by default, need to disable it now.
// TODO(b/292196201) Remove when the display can be disabled before DPC is created.
- logicalDisplay.setEnabledLocked(false);
+ mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, false);
}
if (!isExternalDisplayAllowed()) {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 1dfe03735595..182b05a68028 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -82,11 +82,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.boot.emulator.circular";
// Min and max strengths for even dimmer feature.
private static final float EVEN_DIMMER_MIN_STRENGTH = 0.0f;
- private static final float EVEN_DIMMER_MAX_STRENGTH = 70.0f; // not too dim yet.
+ private static final float EVEN_DIMMER_MAX_STRENGTH = 90.0f;
private static final float BRIGHTNESS_MIN = 0.0f;
- // The brightness at which we start using color matrices rather than backlight,
- // to dim the display
- private static final float BACKLIGHT_COLOR_TRANSITION_POINT = 0.1f;
private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>();
@@ -995,9 +992,12 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private void applyColorMatrixBasedDimming(float brightnessState) {
int strength = (int) (MathUtils.constrainedMap(
- EVEN_DIMMER_MAX_STRENGTH, EVEN_DIMMER_MIN_STRENGTH, // to this range
- BRIGHTNESS_MIN, BACKLIGHT_COLOR_TRANSITION_POINT, // from this range
- brightnessState) + 0.5); // map this (+ rounded up)
+ // to this range:
+ EVEN_DIMMER_MAX_STRENGTH, EVEN_DIMMER_MIN_STRENGTH,
+ // from this range:
+ BRIGHTNESS_MIN, mDisplayDeviceConfig.getEvenDimmerTransitionPoint(),
+ // map this (+ rounded up):
+ brightnessState) + 0.5);
if (mEvenDimmerStrength < 0 // uninitialised
|| MathUtils.abs(mEvenDimmerStrength - strength) > 1
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 8084685eeec9..8b3e4a44bde4 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -31,7 +31,7 @@ import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.BrightnessMappingStrategy;
import com.android.server.display.BrightnessSetting;
import com.android.server.display.DisplayBrightnessState;
-import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -76,6 +76,10 @@ public final class DisplayBrightnessController {
@GuardedBy("mLock")
private float mLastUserSetScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ // Represents if the system has adjusted the brightness based on the user suggested value. Will
+ // be false if the brightness change is coming from a non-user source
+ private boolean mUserSetScreenBrightnessUpdated;
+
// The listener which is to be notified everytime there is a change in the brightness in the
// BrightnessSetting.
private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener;
@@ -138,7 +142,6 @@ public final class DisplayBrightnessController {
public DisplayBrightnessState updateBrightness(
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
int targetDisplayState) {
-
DisplayBrightnessState state;
synchronized (mLock) {
mDisplayBrightnessStrategy = mDisplayBrightnessStrategySelector.selectStrategy(
@@ -246,28 +249,14 @@ public final class DisplayBrightnessController {
}
/**
- * We want to return true if the user has set the screen brightness.
- * RBC on, off, and intensity changes will return false.
- * Slider interactions whilst in RBC will return true, just as when in non-rbc.
+ * Returns if the system has adjusted the brightness based on the user suggested value. Will
+ * be false if the brightness change is coming from a non-user source.
+ *
+ * Todo: 294444204 This is a temporary workaround, and should be moved to the manual brightness
+ * strategy once that is introduced
*/
- public boolean updateUserSetScreenBrightness() {
- synchronized (mLock) {
- if (!BrightnessUtils.isValidBrightnessValue(mPendingScreenBrightness)) {
- return false;
- }
- if (mCurrentScreenBrightness == mPendingScreenBrightness) {
- mPendingScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- setTemporaryBrightnessLocked(PowerManager.BRIGHTNESS_INVALID_FLOAT);
- return false;
- }
- setCurrentScreenBrightnessLocked(mPendingScreenBrightness);
- mLastUserSetScreenBrightness = mPendingScreenBrightness;
- mPendingScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- setTemporaryBrightnessLocked(PowerManager.BRIGHTNESS_INVALID_FLOAT);
- }
- notifyCurrentScreenBrightness();
- return true;
-
+ public boolean getIsUserSetScreenBrightnessUpdated() {
+ return mUserSetScreenBrightnessUpdated;
}
/**
@@ -355,7 +344,7 @@ public final class DisplayBrightnessController {
/**
* TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
*/
- public AutomaticBrightnessStrategy getAutomaticBrightnessStrategy() {
+ public AutomaticBrightnessStrategy2 getAutomaticBrightnessStrategy() {
return mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy();
}
@@ -442,6 +431,33 @@ public final class DisplayBrightnessController {
}
}
+ /**
+ * We want to return true if the user has set the screen brightness.
+ * RBC on, off, and intensity changes will return false.
+ * Slider interactions whilst in RBC will return true, just as when in non-rbc.
+ */
+ @VisibleForTesting
+ boolean updateUserSetScreenBrightness() {
+ mUserSetScreenBrightnessUpdated = false;
+ synchronized (mLock) {
+ if (!BrightnessUtils.isValidBrightnessValue(mPendingScreenBrightness)) {
+ return false;
+ }
+ if (mCurrentScreenBrightness == mPendingScreenBrightness) {
+ mPendingScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ setTemporaryBrightnessLocked(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ return false;
+ }
+ setCurrentScreenBrightnessLocked(mPendingScreenBrightness);
+ mLastUserSetScreenBrightness = mPendingScreenBrightness;
+ mPendingScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ setTemporaryBrightnessLocked(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ }
+ notifyCurrentScreenBrightness();
+ mUserSetScreenBrightnessUpdated = true;
+ return true;
+ }
+
@VisibleForTesting
static class Injector {
DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(Context context,
@@ -470,7 +486,7 @@ public final class DisplayBrightnessController {
* TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
*/
private DisplayBrightnessState addAutomaticBrightnessState(DisplayBrightnessState state) {
- AutomaticBrightnessStrategy autoStrat = getAutomaticBrightnessStrategy();
+ AutomaticBrightnessStrategy2 autoStrat = getAutomaticBrightnessStrategy();
DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from(state);
builder.setShouldUseAutoBrightness(
@@ -526,6 +542,12 @@ public final class DisplayBrightnessController {
private StrategySelectionRequest constructStrategySelectionRequest(
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
int targetDisplayState) {
- return new StrategySelectionRequest(displayPowerRequest, targetDisplayState);
+ boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
+ float lastUserSetScreenBrightness;
+ synchronized (mLock) {
+ lastUserSetScreenBrightness = mLastUserSetScreenBrightness;
+ }
+ return new StrategySelectionRequest(displayPowerRequest, targetDisplayState,
+ lastUserSetScreenBrightness, userSetBrightnessChanged);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 165c24b43ea3..da66879a7f2e 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -27,6 +27,7 @@ import android.view.Display;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2;
import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
@@ -65,7 +66,16 @@ public class DisplayBrightnessStrategySelector {
// The brightness strategy used to manage the brightness state when the request is invalid.
private final InvalidBrightnessStrategy mInvalidBrightnessStrategy;
// Controls brightness when automatic (adaptive) brightness is running.
- private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+ private final AutomaticBrightnessStrategy2 mAutomaticBrightnessStrategy;
+
+ // The automatic strategy which controls the brightness when adaptive mode is ON.
+ private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy1;
+
+ // The deprecated AutomaticBrightnessStrategy. Avoid using it for any new features without
+ // consulting with the display frameworks team. Use {@link AutomaticBrightnessStrategy} instead.
+ // This will be removed once the flag
+ // {@link DisplayManagerFlags#isRefactorDisplayPowerControllerEnabled is fully rolled out
+ private final AutomaticBrightnessStrategy2 mAutomaticBrightnessStrategy2;
// Controls the brightness if adaptive brightness is on and there exists an active offload
// session. Brightness value is provided by the offload session.
@Nullable
@@ -101,7 +111,15 @@ public class DisplayBrightnessStrategySelector {
mBoostBrightnessStrategy = injector.getBoostBrightnessStrategy();
mFollowerBrightnessStrategy = injector.getFollowerBrightnessStrategy(displayId);
mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
- mAutomaticBrightnessStrategy = injector.getAutomaticBrightnessStrategy(context, displayId);
+ mAutomaticBrightnessStrategy1 =
+ (!mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) ? null
+ : injector.getAutomaticBrightnessStrategy1(context, displayId);
+ mAutomaticBrightnessStrategy2 =
+ (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) ? null
+ : injector.getAutomaticBrightnessStrategy2(context, displayId);
+ mAutomaticBrightnessStrategy =
+ (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled())
+ ? mAutomaticBrightnessStrategy1 : mAutomaticBrightnessStrategy2;
if (flags.isDisplayOffloadEnabled()) {
mOffloadBrightnessStrategy = injector.getOffloadBrightnessStrategy();
} else {
@@ -110,7 +128,7 @@ public class DisplayBrightnessStrategySelector {
mDisplayBrightnessStrategies = new DisplayBrightnessStrategy[]{mInvalidBrightnessStrategy,
mScreenOffBrightnessStrategy, mDozeBrightnessStrategy, mFollowerBrightnessStrategy,
mBoostBrightnessStrategy, mOverrideBrightnessStrategy, mTemporaryBrightnessStrategy,
- mOffloadBrightnessStrategy};
+ mAutomaticBrightnessStrategy1, mOffloadBrightnessStrategy};
mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
R.bool.config_allowAutoBrightnessWhileDozing);
mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
@@ -142,6 +160,9 @@ public class DisplayBrightnessStrategySelector {
} else if (BrightnessUtils.isValidBrightnessValue(
mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) {
displayBrightnessStrategy = mTemporaryBrightnessStrategy;
+ } else if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()
+ && isAutomaticBrightnessStrategyValid(strategySelectionRequest)) {
+ displayBrightnessStrategy = mAutomaticBrightnessStrategy1;
} else if (mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
&& mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
mOffloadBrightnessStrategy.getOffloadScreenBrightness())) {
@@ -149,7 +170,8 @@ public class DisplayBrightnessStrategySelector {
}
if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) {
- postProcess(constructStrategySelectionNotifyRequest(displayBrightnessStrategy));
+ postProcess(constructStrategySelectionNotifyRequest(displayBrightnessStrategy,
+ strategySelectionRequest));
}
if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) {
@@ -170,7 +192,7 @@ public class DisplayBrightnessStrategySelector {
return mFollowerBrightnessStrategy;
}
- public AutomaticBrightnessStrategy getAutomaticBrightnessStrategy() {
+ public AutomaticBrightnessStrategy2 getAutomaticBrightnessStrategy() {
return mAutomaticBrightnessStrategy;
}
@@ -206,9 +228,28 @@ public class DisplayBrightnessStrategySelector {
}
}
+ private boolean isAutomaticBrightnessStrategyValid(
+ StrategySelectionRequest strategySelectionRequest) {
+ mAutomaticBrightnessStrategy1.setAutoBrightnessState(
+ strategySelectionRequest.getTargetDisplayState(),
+ mAllowAutoBrightnessWhileDozingConfig,
+ BrightnessReason.REASON_UNKNOWN,
+ strategySelectionRequest.getDisplayPowerRequest().policy,
+ strategySelectionRequest.getLastUserSetScreenBrightness(),
+ strategySelectionRequest.isUserSetBrightnessChanged());
+ return mAutomaticBrightnessStrategy1.isAutoBrightnessValid();
+ }
+
private StrategySelectionNotifyRequest constructStrategySelectionNotifyRequest(
- DisplayBrightnessStrategy selectedDisplayBrightnessStrategy) {
- return new StrategySelectionNotifyRequest(selectedDisplayBrightnessStrategy);
+ DisplayBrightnessStrategy selectedDisplayBrightnessStrategy,
+ StrategySelectionRequest strategySelectionRequest) {
+ return new StrategySelectionNotifyRequest(
+ strategySelectionRequest.getDisplayPowerRequest(),
+ strategySelectionRequest.getTargetDisplayState(),
+ selectedDisplayBrightnessStrategy,
+ strategySelectionRequest.getLastUserSetScreenBrightness(),
+ strategySelectionRequest.isUserSetBrightnessChanged(),
+ isAllowAutoBrightnessWhileDozingConfig());
}
private void postProcess(StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
@@ -263,10 +304,16 @@ public class DisplayBrightnessStrategySelector {
return new InvalidBrightnessStrategy();
}
- AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context, int displayId) {
+ AutomaticBrightnessStrategy getAutomaticBrightnessStrategy1(Context context,
+ int displayId) {
return new AutomaticBrightnessStrategy(context, displayId);
}
+ AutomaticBrightnessStrategy2 getAutomaticBrightnessStrategy2(Context context,
+ int displayId) {
+ return new AutomaticBrightnessStrategy2(context, displayId);
+ }
+
OffloadBrightnessStrategy getOffloadBrightnessStrategy() {
return new OffloadBrightnessStrategy();
}
diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
index d8bd2e459730..6e6c9720bc3a 100644
--- a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
+++ b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
@@ -16,6 +16,8 @@
package com.android.server.display.brightness;
+import android.hardware.display.DisplayManagerInternal;
+
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import java.util.Objects;
@@ -25,11 +27,36 @@ import java.util.Objects;
* DisplayBrightnessStrategy
*/
public final class StrategySelectionNotifyRequest {
+ // The request to change the associated display's state and brightness
+ private DisplayManagerInternal.DisplayPowerRequest mDisplayPowerRequest;
+
+ // The display state to which the screen is switching to
+ private int mTargetDisplayState;
+
// The strategy that was selected with the current request
private final DisplayBrightnessStrategy mSelectedDisplayBrightnessStrategy;
- public StrategySelectionNotifyRequest(DisplayBrightnessStrategy displayBrightnessStrategy) {
+ // The last brightness that was set by the user and not temporary. Set to
+ // PowerManager.BRIGHTNESS_INVALID_FLOAT when a brightness has yet to be recorded.
+ private float mLastUserSetScreenBrightness;
+
+ // Represents if the user set screen brightness was changed or not.
+ private boolean mUserSetBrightnessChanged;
+
+ // True if light sensor is to be used to automatically determine doze screen brightness.
+ private final boolean mAllowAutoBrightnessWhileDozingConfig;
+
+ public StrategySelectionNotifyRequest(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, int targetDisplayState,
+ DisplayBrightnessStrategy displayBrightnessStrategy,
+ float lastUserSetScreenBrightness,
+ boolean userSetBrightnessChanged, boolean allowAutoBrightnessWhileDozingConfig) {
+ mDisplayPowerRequest = displayPowerRequest;
+ mTargetDisplayState = targetDisplayState;
mSelectedDisplayBrightnessStrategy = displayBrightnessStrategy;
+ mLastUserSetScreenBrightness = lastUserSetScreenBrightness;
+ mUserSetBrightnessChanged = userSetBrightnessChanged;
+ mAllowAutoBrightnessWhileDozingConfig = allowAutoBrightnessWhileDozingConfig;
}
public DisplayBrightnessStrategy getSelectedDisplayBrightnessStrategy() {
@@ -43,11 +70,52 @@ public final class StrategySelectionNotifyRequest {
}
StrategySelectionNotifyRequest other = (StrategySelectionNotifyRequest) obj;
return other.getSelectedDisplayBrightnessStrategy()
- == getSelectedDisplayBrightnessStrategy();
+ == getSelectedDisplayBrightnessStrategy()
+ && Objects.equals(mDisplayPowerRequest, other.getDisplayPowerRequest())
+ && mTargetDisplayState == other.getTargetDisplayState()
+ && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged()
+ && mLastUserSetScreenBrightness == other.getLastUserSetScreenBrightness()
+ && mAllowAutoBrightnessWhileDozingConfig
+ == other.isAllowAutoBrightnessWhileDozingConfig();
}
@Override
public int hashCode() {
- return Objects.hash(mSelectedDisplayBrightnessStrategy);
+ return Objects.hash(mSelectedDisplayBrightnessStrategy, mDisplayPowerRequest,
+ mTargetDisplayState, mUserSetBrightnessChanged, mLastUserSetScreenBrightness,
+ mAllowAutoBrightnessWhileDozingConfig);
+ }
+
+ public float getLastUserSetScreenBrightness() {
+ return mLastUserSetScreenBrightness;
+ }
+
+ public boolean isUserSetBrightnessChanged() {
+ return mUserSetBrightnessChanged;
+ }
+
+ public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() {
+ return mDisplayPowerRequest;
+ }
+
+ public int getTargetDisplayState() {
+ return mTargetDisplayState;
+ }
+
+ public boolean isAllowAutoBrightnessWhileDozingConfig() {
+ return mAllowAutoBrightnessWhileDozingConfig;
+ }
+
+ /**
+ * A utility to stringify a StrategySelectionNotifyRequest
+ */
+ public String toString() {
+ return "StrategySelectionNotifyRequest:"
+ + " mDisplayPowerRequest=" + mDisplayPowerRequest
+ + " mTargetDisplayState=" + mTargetDisplayState
+ + " mSelectedDisplayBrightnessStrategy=" + mSelectedDisplayBrightnessStrategy
+ + " mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness
+ + " mUserSetBrightnessChanged=" + mUserSetBrightnessChanged
+ + " mAllowAutoBrightnessWhileDozingConfig=" + mAllowAutoBrightnessWhileDozingConfig;
}
}
diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java
index e6185963ae40..ae745efc8683 100644
--- a/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java
+++ b/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java
@@ -31,10 +31,20 @@ public final class StrategySelectionRequest {
// The display state to which the screen is switching to
private int mTargetDisplayState;
+ // The last brightness that was set by the user and not temporary. Set to
+ // PowerManager.BRIGHTNESS_INVALID_FLOAT when a brightness has yet to be recorded.
+ private float mLastUserSetScreenBrightness;
+
+ // Represents if the user set screen brightness was changed or not.
+ private boolean mUserSetBrightnessChanged;
+
public StrategySelectionRequest(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
- int targetDisplayState) {
+ int targetDisplayState, float lastUserSetScreenBrightness,
+ boolean userSetBrightnessChanged) {
mDisplayPowerRequest = displayPowerRequest;
mTargetDisplayState = targetDisplayState;
+ mLastUserSetScreenBrightness = lastUserSetScreenBrightness;
+ mUserSetBrightnessChanged = userSetBrightnessChanged;
}
public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() {
@@ -45,18 +55,30 @@ public final class StrategySelectionRequest {
return mTargetDisplayState;
}
+
+ public float getLastUserSetScreenBrightness() {
+ return mLastUserSetScreenBrightness;
+ }
+
+ public boolean isUserSetBrightnessChanged() {
+ return mUserSetBrightnessChanged;
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof StrategySelectionRequest)) {
return false;
}
StrategySelectionRequest other = (StrategySelectionRequest) obj;
- return Objects.equals(other.getDisplayPowerRequest(), getDisplayPowerRequest())
- && other.getTargetDisplayState() == getTargetDisplayState();
+ return Objects.equals(mDisplayPowerRequest, other.getDisplayPowerRequest())
+ && mTargetDisplayState == other.getTargetDisplayState()
+ && mLastUserSetScreenBrightness == other.getLastUserSetScreenBrightness()
+ && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged();
}
@Override
public int hashCode() {
- return Objects.hash(mDisplayPowerRequest, mTargetDisplayState);
+ return Objects.hash(mDisplayPowerRequest, mTargetDisplayState,
+ mLastUserSetScreenBrightness, mUserSetBrightnessChanged);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 08d4cfdc265b..4be7332f64e8 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -20,6 +20,7 @@ import static android.hardware.display.DisplayManagerInternal.DisplayPowerReques
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.DisplayManagerInternal;
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
@@ -27,9 +28,11 @@ import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -40,7 +43,8 @@ import java.io.PrintWriter;
* that it is being executed from the power thread, and hence doesn't synchronize
* any of its resources
*/
-public class AutomaticBrightnessStrategy {
+public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2
+ implements DisplayBrightnessStrategy{
private final Context mContext;
// The DisplayId of the associated logical display
private final int mDisplayId;
@@ -88,7 +92,12 @@ public class AutomaticBrightnessStrategy {
@Nullable
private BrightnessConfiguration mBrightnessConfiguration;
+ // Indicates if the strategy is already configured for a request, in which case we wouldn't
+ // want to re-evaluate the auto-brightness state
+ private boolean mIsConfigured;
+
public AutomaticBrightnessStrategy(Context context, int displayId) {
+ super(context, displayId);
mContext = context;
mDisplayId = displayId;
mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
@@ -112,7 +121,7 @@ public class AutomaticBrightnessStrategy {
mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
&& !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
final int autoBrightnessState = mIsAutoBrightnessEnabled
- && brightnessReason != BrightnessReason.REASON_FOLLOWER
+ && brightnessReason != BrightnessReason.REASON_FOLLOWER
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
: mAutoBrightnessDisabledDueToDisplayOff
? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
@@ -120,12 +129,36 @@ public class AutomaticBrightnessStrategy {
accommodateUserBrightnessChanges(userSetBrightnessChanged, lastUserSetScreenBrightness,
policy, targetDisplayState, mBrightnessConfiguration, autoBrightnessState);
+ mIsConfigured = true;
+ }
+
+ public void setIsConfigured(boolean configure) {
+ mIsConfigured = configure;
}
public boolean isAutoBrightnessEnabled() {
return mIsAutoBrightnessEnabled;
}
+ /**
+ * Validates if the auto-brightness strategy is valid or not considering the current system
+ * state.
+ */
+ public boolean isAutoBrightnessValid() {
+ boolean isValid = false;
+ if (isAutoBrightnessEnabled()) {
+ float brightness = (mAutomaticBrightnessController != null)
+ ? mAutomaticBrightnessController.getAutomaticScreenBrightness(null)
+ : PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ if (BrightnessUtils.isValidBrightnessValue(brightness)
+ || brightness == PowerManager.BRIGHTNESS_OFF_FLOAT) {
+ isValid = true;
+ }
+ }
+ setAutoBrightnessApplied(isValid);
+ return isValid;
+ }
+
public boolean isAutoBrightnessDisabledDueToDisplayOff() {
return mAutoBrightnessDisabledDueToDisplayOff;
}
@@ -217,6 +250,29 @@ public class AutomaticBrightnessStrategy {
mTemporaryAutoBrightnessAdjustment = temporaryAutoBrightnessAdjustment;
}
+ @Override
+ public DisplayBrightnessState updateBrightness(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
+ BrightnessEvent brightnessEvent = new BrightnessEvent(mDisplayId);
+ float brightness = getAutomaticScreenBrightness(brightnessEvent);
+ return new DisplayBrightnessState.Builder()
+ .setBrightness(brightness)
+ .setSdrBrightness(brightness)
+ .setBrightnessReason(brightnessReason)
+ .setDisplayBrightnessStrategyName(getName())
+ .setIsSlowChange(hasAppliedAutoBrightness()
+ && !getAutoBrightnessAdjustmentChanged())
+ .setBrightnessEvent(brightnessEvent)
+ .build();
+ }
+
+ @Override
+ public String getName() {
+ return "AutomaticBrightnessStrategy";
+ }
+
/**
* Dumps the state of this class.
*/
@@ -238,6 +294,26 @@ public class AutomaticBrightnessStrategy {
+ mAutoBrightnessAdjustmentReasonsFlags);
}
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ if (!mIsConfigured) {
+ setAutoBrightnessState(strategySelectionNotifyRequest.getTargetDisplayState(),
+ strategySelectionNotifyRequest.isAllowAutoBrightnessWhileDozingConfig(),
+ strategySelectionNotifyRequest.getSelectedDisplayBrightnessStrategy()
+ .getReason(),
+ strategySelectionNotifyRequest.getDisplayPowerRequest().policy,
+ strategySelectionNotifyRequest.getLastUserSetScreenBrightness(),
+ strategySelectionNotifyRequest.isUserSetBrightnessChanged());
+ }
+ mIsConfigured = false;
+ }
+
+ @Override
+ public int getReason() {
+ return BrightnessReason.REASON_AUTOMATIC;
+ }
+
/**
* Indicates if any auto-brightness adjustments have happened since the last auto-brightness was
* set.
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
new file mode 100644
index 000000000000..25e8b2392665
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
@@ -0,0 +1,430 @@
+/*
+ * 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.display.brightness.strategy;
+
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.display.BrightnessConfiguration;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.Display;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+import java.io.PrintWriter;
+
+/**
+ * Helps manage the brightness based on the ambient environment (Ambient Light/lux sensor) using
+ * mappings from lux to nits to brightness, configured in the
+ * {@link com.android.server.display.DisplayDeviceConfig} class. This class inherently assumes
+ * that it is being executed from the power thread, and hence doesn't synchronize
+ * any of its resources
+ *
+ * @deprecated This class is relevant only while the
+ * {@link DisplayManagerFlags#isRefactorDisplayPowerControllerEnabled()} is not fully rolled out.
+ * Till then, please replicated your changes to {@link AutomaticBrightnessStrategy} as well.
+ */
+@Deprecated
+public class AutomaticBrightnessStrategy2 {
+ private final Context mContext;
+ // The DisplayId of the associated logical display
+ private final int mDisplayId;
+ // The last auto brightness adjustment that was set by the user and is not temporary. Set to
+ // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
+ private float mAutoBrightnessAdjustment;
+ // The pending auto brightness adjustment that will take effect on the next power state update.
+ private float mPendingAutoBrightnessAdjustment;
+ // The temporary auto brightness adjustment. This was historically used when a user interacts
+ // with the adjustment slider but hasn't settled on a choice yet.
+ // Set to PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
+ private float mTemporaryAutoBrightnessAdjustment;
+ // Indicates if the temporary auto brightness adjustment has been applied while updating the
+ // associated display brightness
+ private boolean mAppliedTemporaryAutoBrightnessAdjustment;
+ // Indicates if the auto brightness adjustment has happened.
+ private boolean mAutoBrightnessAdjustmentChanged;
+ // Indicates the reasons for the auto-brightness adjustment
+ private int mAutoBrightnessAdjustmentReasonsFlags = 0;
+ // Indicates if the short term model should be reset before fetching the new brightness
+ // Todo(273543270): Short term model is an internal information of
+ // AutomaticBrightnessController and shouldn't be exposed outside of that class
+ private boolean mShouldResetShortTermModel = false;
+ // Remembers whether the auto-brightness has been applied in the latest brightness update.
+ private boolean mAppliedAutoBrightness = false;
+ // The controller for the automatic brightness level.
+ @Nullable
+ private AutomaticBrightnessController mAutomaticBrightnessController;
+ // The system setting denoting if the auto-brightness for the current user is enabled or not
+ private boolean mUseAutoBrightness = false;
+ // Indicates if the auto-brightness is currently enabled or not. It's possible that even if
+ // the user has enabled the auto-brightness from the settings, it is disabled because the
+ // display is off
+ private boolean mIsAutoBrightnessEnabled = false;
+ // Indicates if auto-brightness is disabled due to the display being off. Needed for metric
+ // purposes.
+ private boolean mAutoBrightnessDisabledDueToDisplayOff;
+ // If the auto-brightness model for the last manual changes done by the user.
+ private boolean mIsShortTermModelActive = false;
+
+ // The BrightnessConfiguration currently being used
+ // Todo(273543270): BrightnessConfiguration is an internal implementation detail of
+ // AutomaticBrightnessController, and AutomaticBrightnessStrategy shouldn't be aware of its
+ // existence.
+ @Nullable
+ private BrightnessConfiguration mBrightnessConfiguration;
+
+ public AutomaticBrightnessStrategy2(Context context, int displayId) {
+ mContext = context;
+ mDisplayId = displayId;
+ mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+ mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
+
+ /**
+ * Sets up the automatic brightness states of this class. Also configures
+ * AutomaticBrightnessController accounting for any manual changes made by the user.
+ */
+ public void setAutoBrightnessState(int targetDisplayState,
+ boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy,
+ float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
+ final boolean autoBrightnessEnabledInDoze =
+ allowAutoBrightnessWhileDozingConfig && policy == POLICY_DOZE;
+ mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
+ && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
+ && brightnessReason != BrightnessReason.REASON_OVERRIDE
+ && mAutomaticBrightnessController != null;
+ mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
+ && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
+ final int autoBrightnessState = mIsAutoBrightnessEnabled
+ && brightnessReason != BrightnessReason.REASON_FOLLOWER
+ ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+ : mAutoBrightnessDisabledDueToDisplayOff
+ ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+ : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
+
+ accommodateUserBrightnessChanges(userSetBrightnessChanged, lastUserSetScreenBrightness,
+ policy, targetDisplayState, mBrightnessConfiguration, autoBrightnessState);
+ }
+
+ public boolean isAutoBrightnessEnabled() {
+ return mIsAutoBrightnessEnabled;
+ }
+
+ public boolean isAutoBrightnessDisabledDueToDisplayOff() {
+ return mAutoBrightnessDisabledDueToDisplayOff;
+ }
+
+ /**
+ * Updates the {@link BrightnessConfiguration} that is currently being used by the associated
+ * display.
+ */
+ public void setBrightnessConfiguration(BrightnessConfiguration brightnessConfiguration,
+ boolean shouldResetShortTermModel) {
+ mBrightnessConfiguration = brightnessConfiguration;
+ setShouldResetShortTermModel(shouldResetShortTermModel);
+ }
+
+ /**
+ * Promotes the pending auto-brightness adjustments which are yet to be applied to the current
+ * adjustments. Note that this is not applying the new adjustments to the AutoBrightness mapping
+ * strategies, but is only accommodating the changes in this class.
+ */
+ public boolean processPendingAutoBrightnessAdjustments() {
+ mAutoBrightnessAdjustmentChanged = false;
+ if (Float.isNaN(mPendingAutoBrightnessAdjustment)) {
+ return false;
+ }
+ if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) {
+ mPendingAutoBrightnessAdjustment = Float.NaN;
+ return false;
+ }
+ mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
+ mPendingAutoBrightnessAdjustment = Float.NaN;
+ mTemporaryAutoBrightnessAdjustment = Float.NaN;
+ mAutoBrightnessAdjustmentChanged = true;
+ return true;
+ }
+
+ /**
+ * Updates the associated AutomaticBrightnessController
+ */
+ public void setAutomaticBrightnessController(
+ AutomaticBrightnessController automaticBrightnessController) {
+ if (automaticBrightnessController == mAutomaticBrightnessController) {
+ return;
+ }
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.stop();
+ }
+ mAutomaticBrightnessController = automaticBrightnessController;
+ }
+
+ /**
+ * Returns if the auto-brightness of the associated display has been enabled or not
+ */
+ public boolean shouldUseAutoBrightness() {
+ return mUseAutoBrightness;
+ }
+
+ /**
+ * Sets the auto-brightness state of the associated display. Called when the user makes a change
+ * in the system setting to enable/disable the auto-brightness.
+ */
+ public void setUseAutoBrightness(boolean useAutoBrightness) {
+ mUseAutoBrightness = useAutoBrightness;
+ }
+
+ /**
+ * Returns if the user made brightness change events(Typically when they interact with the
+ * brightness slider) were accommodated in the auto-brightness mapping strategies. This doesn't
+ * account for the latest changes that have been made by the user.
+ */
+ public boolean isShortTermModelActive() {
+ return mIsShortTermModelActive;
+ }
+
+ /**
+ * Sets the pending auto-brightness adjustments in the system settings. Executed
+ * when there is a change in the brightness system setting, or when there is a user switch.
+ */
+ public void updatePendingAutoBrightnessAdjustments() {
+ final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
+ mPendingAutoBrightnessAdjustment = Float.isNaN(adj) ? Float.NaN
+ : BrightnessUtils.clampBrightnessAdjustment(adj);
+ }
+
+ /**
+ * Sets the temporary auto-brightness adjustments
+ */
+ public void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
+ mTemporaryAutoBrightnessAdjustment = temporaryAutoBrightnessAdjustment;
+ }
+
+ /**
+ * Dumps the state of this class.
+ */
+ public void dump(PrintWriter writer) {
+ writer.println("AutomaticBrightnessStrategy:");
+ writer.println(" mDisplayId=" + mDisplayId);
+ writer.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
+ writer.println(" mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
+ writer.println(
+ " mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
+ writer.println(" mShouldResetShortTermModel=" + mShouldResetShortTermModel);
+ writer.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness);
+ writer.println(" mAutoBrightnessAdjustmentChanged=" + mAutoBrightnessAdjustmentChanged);
+ writer.println(" mAppliedTemporaryAutoBrightnessAdjustment="
+ + mAppliedTemporaryAutoBrightnessAdjustment);
+ writer.println(" mUseAutoBrightness=" + mUseAutoBrightness);
+ writer.println(" mWasShortTermModelActive=" + mIsShortTermModelActive);
+ writer.println(" mAutoBrightnessAdjustmentReasonsFlags="
+ + mAutoBrightnessAdjustmentReasonsFlags);
+ }
+
+ /**
+ * Indicates if any auto-brightness adjustments have happened since the last auto-brightness was
+ * set.
+ */
+ public boolean getAutoBrightnessAdjustmentChanged() {
+ return mAutoBrightnessAdjustmentChanged;
+ }
+
+ /**
+ * Returns whether the latest temporary auto-brightness adjustments have been applied or not
+ */
+ public boolean isTemporaryAutoBrightnessAdjustmentApplied() {
+ return mAppliedTemporaryAutoBrightnessAdjustment;
+ }
+
+ /**
+ * Evaluates the target automatic brightness of the associated display.
+ * @param brightnessEvent Event object to populate with details about why the specific
+ * brightness was chosen.
+ */
+ public float getAutomaticScreenBrightness(BrightnessEvent brightnessEvent) {
+ float brightness = (mAutomaticBrightnessController != null)
+ ? mAutomaticBrightnessController.getAutomaticScreenBrightness(brightnessEvent)
+ : PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ adjustAutomaticBrightnessStateIfValid(brightness);
+ return brightness;
+ }
+
+ /**
+ * Get the automatic screen brightness based on the last observed lux reading. Used e.g. when
+ * entering doze - we disable the light sensor, invalidate the lux, but we still need to set
+ * the initial brightness in doze mode.
+ * @param brightnessEvent Event object to populate with details about why the specific
+ * brightness was chosen.
+ */
+ public float getAutomaticScreenBrightnessBasedOnLastObservedLux(
+ BrightnessEvent brightnessEvent) {
+ float brightness = (mAutomaticBrightnessController != null)
+ ? mAutomaticBrightnessController
+ .getAutomaticScreenBrightnessBasedOnLastObservedLux(brightnessEvent)
+ : PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ adjustAutomaticBrightnessStateIfValid(brightness);
+ return brightness;
+ }
+
+ /**
+ * Gets the auto-brightness adjustment flag change reason
+ */
+ public int getAutoBrightnessAdjustmentReasonsFlags() {
+ return mAutoBrightnessAdjustmentReasonsFlags;
+ }
+
+ /**
+ * Returns if the auto brightness has been applied
+ */
+ public boolean hasAppliedAutoBrightness() {
+ return mAppliedAutoBrightness;
+ }
+
+ /**
+ * Used to adjust the state of this class when the automatic brightness value for the
+ * associated display is valid
+ */
+ @VisibleForTesting
+ void adjustAutomaticBrightnessStateIfValid(float brightnessState) {
+ mAutoBrightnessAdjustmentReasonsFlags = isTemporaryAutoBrightnessAdjustmentApplied()
+ ? BrightnessReason.ADJUSTMENT_AUTO_TEMP
+ : BrightnessReason.ADJUSTMENT_AUTO;
+ float newAutoBrightnessAdjustment =
+ (mAutomaticBrightnessController != null)
+ ? mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()
+ : 0.0f;
+ if (!Float.isNaN(newAutoBrightnessAdjustment)
+ && mAutoBrightnessAdjustment != newAutoBrightnessAdjustment) {
+ // If the auto-brightness controller has decided to change the adjustment value
+ // used, make sure that's reflected in settings.
+ putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment);
+ } else {
+ mAutoBrightnessAdjustmentReasonsFlags = 0;
+ }
+ }
+
+ /**
+ * Sets up the system to reset the short term model. Note that this will not reset the model
+ * right away, but ensures that the reset happens whenever the next brightness change happens
+ */
+ @VisibleForTesting
+ void setShouldResetShortTermModel(boolean shouldResetShortTermModel) {
+ mShouldResetShortTermModel = shouldResetShortTermModel;
+ }
+
+ @VisibleForTesting
+ boolean shouldResetShortTermModel() {
+ return mShouldResetShortTermModel;
+ }
+
+ @VisibleForTesting
+ float getAutoBrightnessAdjustment() {
+ return mAutoBrightnessAdjustment;
+ }
+
+ @VisibleForTesting
+ float getPendingAutoBrightnessAdjustment() {
+ return mPendingAutoBrightnessAdjustment;
+ }
+
+ @VisibleForTesting
+ float getTemporaryAutoBrightnessAdjustment() {
+ return mTemporaryAutoBrightnessAdjustment;
+ }
+
+ @VisibleForTesting
+ void putAutoBrightnessAdjustmentSetting(float adjustment) {
+ if (mDisplayId == Display.DEFAULT_DISPLAY) {
+ mAutoBrightnessAdjustment = adjustment;
+ Settings.System.putFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
+ UserHandle.USER_CURRENT);
+ }
+ }
+
+ /**
+ * Sets if the auto-brightness is applied on the latest brightness change.
+ */
+ public void setAutoBrightnessApplied(boolean autoBrightnessApplied) {
+ mAppliedAutoBrightness = autoBrightnessApplied;
+ }
+
+ /**
+ * Accommodates the latest manual changes made by the user. Also updates {@link
+ * AutomaticBrightnessController} about the changes and configures it accordingly.
+ */
+ @VisibleForTesting
+ void accommodateUserBrightnessChanges(boolean userSetBrightnessChanged,
+ float lastUserSetScreenBrightness, int policy, int displayState,
+ BrightnessConfiguration brightnessConfiguration, int autoBrightnessState) {
+ // Update the pending auto-brightness adjustments if any. This typically checks and adjusts
+ // the state of the class if the user moves the brightness slider and has settled to a
+ // different value
+ processPendingAutoBrightnessAdjustments();
+ // Update the temporary auto-brightness adjustments if any. This typically checks and
+ // adjusts the state of this class if the user is in the process of moving the brightness
+ // slider, but hasn't settled to any value yet
+ float autoBrightnessAdjustment = updateTemporaryAutoBrightnessAdjustments();
+ mIsShortTermModelActive = false;
+ // Configure auto-brightness.
+ if (mAutomaticBrightnessController != null) {
+ // Accommodate user changes if any in the auto-brightness model
+ mAutomaticBrightnessController.configure(autoBrightnessState,
+ brightnessConfiguration,
+ lastUserSetScreenBrightness,
+ userSetBrightnessChanged, autoBrightnessAdjustment,
+ mAutoBrightnessAdjustmentChanged, policy, displayState,
+ mShouldResetShortTermModel);
+ mShouldResetShortTermModel = false;
+ // We take note if the user brightness point is still being used in the current
+ // auto-brightness model.
+ mIsShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
+ }
+ }
+
+ /**
+ * Evaluates if there are any temporary auto-brightness adjustments which is not applied yet.
+ * Temporary brightness adjustments happen when the user moves the brightness slider in the
+ * auto-brightness mode, but hasn't settled to a value yet
+ */
+ private float updateTemporaryAutoBrightnessAdjustments() {
+ mAppliedTemporaryAutoBrightnessAdjustment =
+ !Float.isNaN(mTemporaryAutoBrightnessAdjustment);
+ // We do not update the mAutoBrightnessAdjustment with mTemporaryAutoBrightnessAdjustment
+ // since we have not settled to a value yet
+ return mAppliedTemporaryAutoBrightnessAdjustment
+ ? mTemporaryAutoBrightnessAdjustment : mAutoBrightnessAdjustment;
+ }
+
+ /**
+ * Returns the auto-brightness adjustment that is set in the system setting.
+ */
+ private float getAutoBrightnessAdjustmentSetting() {
+ final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
+ return Float.isNaN(adj) ? 0.0f : BrightnessUtils.clampBrightnessAdjustment(adj);
+ }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
index 11edde9caaf7..9c1aceab410c 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
@@ -53,6 +53,11 @@ public class BoostBrightnessStrategy implements DisplayBrightnessStrategy {
}
@Override
+ public int getReason() {
+ return BrightnessReason.REASON_BOOST;
+ }
+
+ @Override
public void dump(PrintWriter writer) {}
@Override
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
index 7b49957a7e31..61dd6d5bb98c 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
@@ -45,6 +45,11 @@ public interface DisplayBrightnessStrategy {
String getName();
/**
+ * Returns the reason for the change of the brightness
+ */
+ int getReason();
+
+ /**
* Dumps the state of the Strategy
* @param writer
*/
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
index 5afdc42b9eee..1f7efd11009e 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
@@ -53,4 +53,9 @@ public class DozeBrightnessStrategy implements DisplayBrightnessStrategy {
StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
// DO NOTHING
}
+
+ @Override
+ public int getReason() {
+ return BrightnessReason.REASON_DOZE;
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
index 0650c1c9dc62..baac2769f3b5 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
@@ -89,4 +89,9 @@ public class FollowerBrightnessStrategy implements DisplayBrightnessStrategy {
StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
// DO NOTHING
}
+
+ @Override
+ public int getReason() {
+ return BrightnessReason.REASON_FOLLOWER;
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
index bf37ee0a9666..4abd0284838f 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
@@ -51,4 +51,9 @@ public class InvalidBrightnessStrategy implements DisplayBrightnessStrategy {
StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
// DO NOTHING
}
+
+ @Override
+ public int getReason() {
+ return BrightnessReason.REASON_UNKNOWN;
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
index d2bb1e284256..64dc47ce4163 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
@@ -79,4 +79,9 @@ public class OffloadBrightnessStrategy implements DisplayBrightnessStrategy {
StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
// DO NOTHING
}
+
+ @Override
+ public int getReason() {
+ return BrightnessReason.REASON_OFFLOAD;
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
index 653170c886ea..9605a8823141 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
@@ -52,4 +52,9 @@ public class OverrideBrightnessStrategy implements DisplayBrightnessStrategy {
StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
// DO NOTHING
}
+
+ @Override
+ public int getReason() {
+ return BrightnessReason.REASON_OVERRIDE;
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
index f0cce23c6f45..c9dc29889e31 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
@@ -53,4 +53,9 @@ public class ScreenOffBrightnessStrategy implements DisplayBrightnessStrategy {
StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
// DO NOTHING
}
+
+ @Override
+ public int getReason() {
+ return BrightnessReason.REASON_SCREEN_OFF;
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
index 91e1d091ade3..6a691d19c1bd 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
@@ -80,4 +80,9 @@ public class TemporaryBrightnessStrategy implements DisplayBrightnessStrategy {
StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
// DO NOTHING
}
+
+ @Override
+ public int getReason() {
+ return BrightnessReason.REASON_TEMPORARY;
+ }
}
diff --git a/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
index 555636538893..7e2e10a7639f 100644
--- a/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
@@ -66,6 +66,10 @@ public class EvenDimmerBrightnessData {
* Spline, mapping between backlight and brightness
*/
public final Spline mBacklightToBrightness;
+
+ /**
+ * Spline, mapping the minimum nits for each lux condition.
+ */
public final Spline mMinLuxToNits;
@VisibleForTesting
@@ -114,34 +118,35 @@ public class EvenDimmerBrightnessData {
return null;
}
- List<Float> nitsList = lbm.getNits();
- List<Float> backlightList = lbm.getBacklight();
- List<Float> brightnessList = lbm.getBrightness();
- float transitionPoints = lbm.getTransitionPoint().floatValue();
+ ComprehensiveBrightnessMap map = lbm.getBrightnessMapping();
+ if (map == null) {
+ return null;
+ }
+ String interpolation = map.getInterpolation();
- if (nitsList.isEmpty()
- || backlightList.size() != brightnessList.size()
- || backlightList.size() != nitsList.size()) {
- Slog.e(TAG, "Invalid even dimmer array lengths");
+ List<BrightnessPoint> brightnessPoints = map.getBrightnessPoint();
+ if (brightnessPoints.isEmpty()) {
return null;
}
- float[] nits = new float[nitsList.size()];
- float[] backlight = new float[nitsList.size()];
- float[] brightness = new float[nitsList.size()];
+ float[] nits = new float[brightnessPoints.size()];
+ float[] backlight = new float[brightnessPoints.size()];
+ float[] brightness = new float[brightnessPoints.size()];
- for (int i = 0; i < nitsList.size(); i++) {
- nits[i] = nitsList.get(i);
- backlight[i] = backlightList.get(i);
- brightness[i] = brightnessList.get(i);
+ for (int i = 0; i < brightnessPoints.size(); i++) {
+ BrightnessPoint val = brightnessPoints.get(i);
+ nits[i] = val.getNits().floatValue();
+ backlight[i] = val.getBacklight().floatValue();
+ brightness[i] = val.getBrightness().floatValue();
}
- final NitsMap map = lbm.getLuxToMinimumNitsMap();
- if (map == null) {
+ float transitionPoint = lbm.getTransitionPoint().floatValue();
+ final NitsMap minimumNitsMap = lbm.getLuxToMinimumNitsMap();
+ if (minimumNitsMap == null) {
Slog.e(TAG, "Invalid min lux to nits mapping");
return null;
}
- final List<Point> points = map.getPoint();
+ final List<Point> points = minimumNitsMap.getPoint();
final int size = points.size();
float[] minLux = new float[size];
@@ -164,7 +169,18 @@ public class EvenDimmerBrightnessData {
++i;
}
- return new EvenDimmerBrightnessData(transitionPoints, nits, backlight, brightness,
+ // Explicitly choose linear interpolation.
+ if ("linear".equals(interpolation)) {
+ return new EvenDimmerBrightnessData(transitionPoint, nits, backlight, brightness,
+ new Spline.LinearSpline(backlight, nits),
+ new Spline.LinearSpline(nits, backlight),
+ new Spline.LinearSpline(brightness, backlight),
+ new Spline.LinearSpline(backlight, brightness),
+ new Spline.LinearSpline(minLux, minNits)
+ );
+ }
+
+ return new EvenDimmerBrightnessData(transitionPoint, nits, backlight, brightness,
Spline.createSpline(backlight, nits),
Spline.createSpline(nits, backlight),
Spline.createSpline(brightness, backlight),
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index d084d1c26b1c..a862b6e8f8f4 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -105,6 +105,7 @@ import java.util.function.IntSupplier;
* picked by the system based on system-wide and display-specific configuration.
*/
public class DisplayModeDirector {
+
public static final float SYNCHRONIZED_REFRESH_RATE_TARGET = DEFAULT_LOW_REFRESH_RATE;
public static final float SYNCHRONIZED_REFRESH_RATE_TOLERANCE = 1;
private static final String TAG = "DisplayModeDirector";
@@ -149,6 +150,9 @@ public class DisplayModeDirector {
// A map from the display ID to the default mode of that display.
private SparseArray<Display.Mode> mDefaultModeByDisplay;
+ // a map from display id to vrr support
+ private SparseBooleanArray mVrrSupportedByDisplay;
+
private BrightnessObserver mBrightnessObserver;
private DesiredDisplayModeSpecsListener mDesiredDisplayModeSpecsListener;
@@ -189,8 +193,6 @@ public class DisplayModeDirector {
private final DisplayManagerFlags mDisplayManagerFlags;
- private final boolean mDvrrSupported;
-
public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler,
@NonNull DisplayManagerFlags displayManagerFlags) {
@@ -200,8 +202,6 @@ public class DisplayModeDirector {
public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler,
@NonNull Injector injector,
@NonNull DisplayManagerFlags displayManagerFlags) {
- mDvrrSupported = context.getResources().getBoolean(
- com.android.internal.R.bool.config_supportsDvrr);
mIsDisplayResolutionRangeVotingEnabled = displayManagerFlags
.isDisplayResolutionRangeVotingEnabled();
mIsUserPreferredModeVoteEnabled = displayManagerFlags.isUserPreferredModeVoteEnabled();
@@ -219,23 +219,23 @@ public class DisplayModeDirector {
displayManagerFlags.isRefreshRateVotingTelemetryEnabled());
mSupportedModesByDisplay = new SparseArray<>();
mDefaultModeByDisplay = new SparseArray<>();
+ mVrrSupportedByDisplay = new SparseBooleanArray();
mAppRequestObserver = new AppRequestObserver();
mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
- mSettingsObserver = new SettingsObserver(context, handler, mDvrrSupported,
- displayManagerFlags);
- mBrightnessObserver = new BrightnessObserver(context, handler, injector, mDvrrSupported,
+ mSettingsObserver = new SettingsObserver(context, handler, displayManagerFlags);
+ mBrightnessObserver = new BrightnessObserver(context, handler, injector,
displayManagerFlags);
mDefaultDisplayDeviceConfig = null;
mUdfpsObserver = new UdfpsObserver();
mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked,
mVotesStatsReporter);
- mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage);
+ mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage, injector);
mSensorObserver = new ProximitySensorObserver(mVotesStorage, injector);
mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage);
mHbmObserver = new HbmObserver(injector, mVotesStorage, BackgroundThread.getHandler(),
mDeviceConfigDisplaySettings);
- if (mDvrrSupported && displayManagerFlags.isRestrictDisplayModesEnabled()) {
+ if (displayManagerFlags.isRestrictDisplayModesEnabled()) {
mSystemRequestObserver = new SystemRequestObserver(mVotesStorage);
} else {
mSystemRequestObserver = null;
@@ -315,7 +315,8 @@ public class DisplayModeDirector {
List<Display.Mode> availableModes = new ArrayList<>();
availableModes.add(defaultMode);
VoteSummary primarySummary = new VoteSummary(mIsDisplayResolutionRangeVotingEnabled,
- mDvrrSupported, mLoggingEnabled, mSupportsFrameRateOverride);
+ mVrrSupportedByDisplay.get(displayId),
+ mLoggingEnabled, mSupportsFrameRateOverride);
int lowestConsideredPriority = Vote.MIN_PRIORITY;
int highestConsideredPriority = Vote.MAX_PRIORITY;
@@ -355,7 +356,8 @@ public class DisplayModeDirector {
}
VoteSummary appRequestSummary = new VoteSummary(mIsDisplayResolutionRangeVotingEnabled,
- mDvrrSupported, mLoggingEnabled, mSupportsFrameRateOverride);
+ mVrrSupportedByDisplay.get(displayId),
+ mLoggingEnabled, mSupportsFrameRateOverride);
appRequestSummary.applyVotes(votes,
Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF,
@@ -442,6 +444,15 @@ public class DisplayModeDirector {
return mAppRequestObserver;
}
+ private boolean isVrrSupportedByAnyDisplayLocked() {
+ for (int i = 0; i < mVrrSupportedByDisplay.size(); i++) {
+ if (mVrrSupportedByDisplay.valueAt(i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Sets the desiredDisplayModeSpecsListener for changes to display mode and refresh rate ranges.
*/
@@ -539,7 +550,13 @@ public class DisplayModeDirector {
*/
public void requestDisplayModes(IBinder token, int displayId, int[] modeIds) {
if (mSystemRequestObserver != null) {
- mSystemRequestObserver.requestDisplayModes(token, displayId, modeIds);
+ boolean vrrSupported;
+ synchronized (mLock) {
+ vrrSupported = mVrrSupportedByDisplay.get(displayId);
+ }
+ if (vrrSupported) {
+ mSystemRequestObserver.requestDisplayModes(token, displayId, modeIds);
+ }
}
}
@@ -627,6 +644,11 @@ public class DisplayModeDirector {
}
@VisibleForTesting
+ void injectVrrByDisplay(SparseBooleanArray vrrByDisplay) {
+ mVrrSupportedByDisplay = vrrByDisplay;
+ }
+
+ @VisibleForTesting
void injectVotesByDisplay(SparseArray<SparseArray<Vote>> votesByDisplay) {
mVotesStorage.injectVotesByDisplay(votesByDisplay);
}
@@ -906,11 +928,11 @@ public class DisplayModeDirector {
private float mDefaultPeakRefreshRate;
private float mDefaultRefreshRate;
- SettingsObserver(@NonNull Context context, @NonNull Handler handler, boolean dvrrSupported,
+ SettingsObserver(@NonNull Context context, @NonNull Handler handler,
DisplayManagerFlags flags) {
super(handler);
mContext = context;
- mVsynLowPowerVoteEnabled = dvrrSupported && flags.isVsyncLowPowerVoteEnabled();
+ mVsynLowPowerVoteEnabled = flags.isVsyncLowPowerVoteEnabled();
// We don't want to load from the DeviceConfig while constructing since this leads to
// a spike in the latency of DisplayManagerService startup. This happens because
// reading from the DeviceConfig is an intensive IO operation and having it in the
@@ -1020,7 +1042,7 @@ public class DisplayModeDirector {
boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0;
final Vote vote;
- if (inLowPowerMode && mVsynLowPowerVoteEnabled) {
+ if (inLowPowerMode && mVsynLowPowerVoteEnabled && isVrrSupportedByAnyDisplayLocked()) {
vote = Vote.forSupportedRefreshRates(List.of(
new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f,
/* vsyncRate= */ 240f),
@@ -1190,8 +1212,8 @@ public class DisplayModeDirector {
/**
* Sets refresh rates from app request
*/
- public void setAppRequest(int displayId, int modeId, float requestedMinRefreshRateRange,
- float requestedMaxRefreshRateRange) {
+ public void setAppRequest(int displayId, int modeId,
+ float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) {
synchronized (mLock) {
setAppRequestedModeLocked(displayId, modeId);
setAppPreferredRefreshRateRangeLocked(displayId, requestedMinRefreshRateRange,
@@ -1204,7 +1226,6 @@ public class DisplayModeDirector {
if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) {
return;
}
-
final Vote baseModeRefreshRateVote;
final Vote sizeVote;
if (requestedMode != null) {
@@ -1295,13 +1316,16 @@ public class DisplayModeDirector {
private final Context mContext;
private final Handler mHandler;
private final VotesStorage mVotesStorage;
+
+ private DisplayManagerInternal mDisplayManagerInternal;
private int mExternalDisplayPeakWidth;
private int mExternalDisplayPeakHeight;
private int mExternalDisplayPeakRefreshRate;
private final boolean mRefreshRateSynchronizationEnabled;
private final Set<Integer> mExternalDisplaysConnected = new HashSet<>();
- DisplayObserver(Context context, Handler handler, VotesStorage votesStorage) {
+ DisplayObserver(Context context, Handler handler, VotesStorage votesStorage,
+ Injector injector) {
mContext = context;
mHandler = handler;
mVotesStorage = votesStorage;
@@ -1330,6 +1354,7 @@ public class DisplayModeDirector {
}
public void observe() {
+ mDisplayManagerInternal = mInjector.getDisplayManagerInternal();
mInjector.registerDisplayListener(this, mHandler);
// Populate existing displays
@@ -1342,17 +1367,21 @@ public class DisplayModeDirector {
modes.put(displayId, info.supportedModes);
defaultModes.put(displayId, info.getDefaultMode());
}
+ boolean vrrSupportedByDefaultDisplay = mDisplayManagerInternal
+ .isVrrSupportEnabled(Display.DEFAULT_DISPLAY);
synchronized (mLock) {
final int size = modes.size();
for (int i = 0; i < size; i++) {
mSupportedModesByDisplay.put(modes.keyAt(i), modes.valueAt(i));
mDefaultModeByDisplay.put(defaultModes.keyAt(i), defaultModes.valueAt(i));
}
+ mVrrSupportedByDisplay.put(Display.DEFAULT_DISPLAY, vrrSupportedByDefaultDisplay);
}
}
@Override
public void onDisplayAdded(int displayId) {
+ updateVrrStatus(displayId);
DisplayInfo displayInfo = getDisplayInfo(displayId);
updateDisplayModes(displayId, displayInfo);
updateLayoutLimitedFrameRate(displayId, displayInfo);
@@ -1366,6 +1395,7 @@ public class DisplayModeDirector {
synchronized (mLock) {
mSupportedModesByDisplay.remove(displayId);
mDefaultModeByDisplay.remove(displayId);
+ mVrrSupportedByDisplay.delete(displayId);
mSettingsObserver.removeRefreshRateSetting(displayId);
}
updateLayoutLimitedFrameRate(displayId, null);
@@ -1376,6 +1406,7 @@ public class DisplayModeDirector {
@Override
public void onDisplayChanged(int displayId) {
+ updateVrrStatus(displayId);
DisplayInfo displayInfo = getDisplayInfo(displayId);
updateDisplayModes(displayId, displayInfo);
updateLayoutLimitedFrameRate(displayId, displayInfo);
@@ -1505,6 +1536,13 @@ public class DisplayModeDirector {
mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, null);
}
+ private void updateVrrStatus(int displayId) {
+ boolean isVrrSupported = mDisplayManagerInternal.isVrrSupportEnabled(displayId);
+ synchronized (mLock) {
+ mVrrSupportedByDisplay.put(displayId, isVrrSupported);
+ }
+ }
+
private void updateDisplayModes(int displayId, @Nullable DisplayInfo info) {
if (info == null) {
return;
@@ -1631,7 +1669,7 @@ public class DisplayModeDirector {
private @Temperature.ThrottlingStatus int mThermalStatus = Temperature.THROTTLING_NONE;
BrightnessObserver(Context context, Handler handler, Injector injector,
- boolean dvrrSupported , DisplayManagerFlags flags) {
+ DisplayManagerFlags flags) {
mContext = context;
mHandler = handler;
mInjector = injector;
@@ -1639,7 +1677,7 @@ public class DisplayModeDirector {
/* attemptReadFromFeatureParams= */ false);
mRefreshRateInHighZone = context.getResources().getInteger(
R.integer.config_fixedRefreshRateInHighZone);
- mVsyncLowLightBlockingVoteEnabled = dvrrSupported && flags.isVsyncLowLightVoteEnabled();
+ mVsyncLowLightBlockingVoteEnabled = flags.isVsyncLowLightVoteEnabled();
}
/**
@@ -2225,7 +2263,8 @@ public class DisplayModeDirector {
}
}
- if (mVsyncLowLightBlockingVoteEnabled) {
+ if (mVsyncLowLightBlockingVoteEnabled
+ && mVrrSupportedByDisplay.get(Display.DEFAULT_DISPLAY)) {
refreshRateSwitchingVote = Vote.forSupportedRefreshRatesAndDisableSwitching(
List.of(
new SupportedRefreshRatesVote.RefreshRates(
diff --git a/services/core/java/com/android/server/flags/compaction.aconfig b/services/core/java/com/android/server/flags/compaction.aconfig
index 067a1c996a02..58cc56029ea1 100644
--- a/services/core/java/com/android/server/flags/compaction.aconfig
+++ b/services/core/java/com/android/server/flags/compaction.aconfig
@@ -1,5 +1,4 @@
package: "com.android.server.flags"
-container: "system"
flag {
name: "disable_system_compaction"
diff --git a/services/core/java/com/android/server/flags/pinner.aconfig b/services/core/java/com/android/server/flags/pinner.aconfig
index 16a45cd87fd7..606a6be29511 100644
--- a/services/core/java/com/android/server/flags/pinner.aconfig
+++ b/services/core/java/com/android/server/flags/pinner.aconfig
@@ -1,5 +1,4 @@
package: "com.android.server.flags"
-container: "system"
flag {
name: "pin_webview"
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
index 8e0eb0509a78..10b5eff06e0c 100644
--- a/services/core/java/com/android/server/flags/services.aconfig
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -1,5 +1,4 @@
package: "com.android.server.flags"
-container: "system"
flag {
namespace: "wear_frameworks"
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
index 5be0735c23b2..d494be5de74f 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
@@ -17,6 +17,7 @@
package com.android.server.grammaticalinflection;
import android.app.backup.BackupManager;
+import android.content.AttributionSource;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -30,6 +31,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
+import java.nio.ByteBuffer;
import java.time.Clock;
import java.time.Duration;
import java.util.HashMap;
@@ -47,6 +49,7 @@ public class GrammaticalInflectionBackupHelper {
private final PackageManager mPackageManager;
private final GrammaticalInflectionService mGrammaticalGenderService;
private final Clock mClock;
+ private final AttributionSource mAttributionSource;
static class StagedData {
final long mCreationTimeMillis;
@@ -58,8 +61,9 @@ public class GrammaticalInflectionBackupHelper {
}
}
- public GrammaticalInflectionBackupHelper(GrammaticalInflectionService grammaticalGenderService,
- PackageManager packageManager) {
+ public GrammaticalInflectionBackupHelper(AttributionSource attributionSource,
+ GrammaticalInflectionService grammaticalGenderService, PackageManager packageManager) {
+ mAttributionSource = attributionSource;
mGrammaticalGenderService = grammaticalGenderService;
mPackageManager = packageManager;
mClock = Clock.systemUTC();
@@ -115,6 +119,23 @@ public class GrammaticalInflectionBackupHelper {
}
}
+ /**
+ * Returns the system-gender to be backed up as a data-blob.
+ */
+ public byte[] getSystemBackupPayload(int userId) {
+ int gender = mGrammaticalGenderService.getSystemGrammaticalGender(mAttributionSource,
+ userId);
+ return intToByteArray(gender);
+ }
+
+ /**
+ * Restores the system-gender that were previously backed up.
+ */
+ public void applyRestoredSystemPayload(byte[] payload, int userId) {
+ int gender = convertByteArrayToInt(payload);
+ mGrammaticalGenderService.setSystemWideGrammaticalGender(gender, userId);
+ }
+
private boolean hasSetBeforeRestoring(String pkgName, int userId) {
return mGrammaticalGenderService.getApplicationGrammaticalGender(pkgName, userId)
!= Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
@@ -157,6 +178,17 @@ public class GrammaticalInflectionBackupHelper {
}
}
+ private byte[] intToByteArray(final int gender) {
+ ByteBuffer bb = ByteBuffer.allocate(4);
+ bb.putInt(gender);
+ return bb.array();
+ }
+
+ private int convertByteArrayToInt(byte[] intBytes) {
+ ByteBuffer byteBuffer = ByteBuffer.wrap(intBytes);
+ return byteBuffer.getInt();
+ }
+
private HashMap<String, Integer> readFromByteArray(byte[] payload) {
HashMap<String, Integer> data = new HashMap<>();
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
index c2c82edee33d..2816d0835db1 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
@@ -62,5 +62,16 @@ public abstract class GrammaticalInflectionManagerInternal {
* Whether the package can get the system grammatical gender or not.
*/
public abstract boolean canGetSystemGrammaticalGender(int uid, @Nullable String packageName);
+
+
+ /**
+ * Returns the system-gender to be backed up as a data-blob.
+ */
+ public abstract @Nullable byte[] getSystemBackupPayload(int userId);
+
+ /**
+ * Restores the system-gender that were previously backed up.
+ */
+ public abstract void applyRestoredSystemPayload(byte[] payload, int userId);
}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 96d4bda67b16..93a71b9a40a2 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -102,7 +102,8 @@ public class GrammaticalInflectionService extends SystemService {
mContext = context;
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- mBackupHelper = new GrammaticalInflectionBackupHelper(this, context.getPackageManager());
+ mBackupHelper = new GrammaticalInflectionBackupHelper(mContext.getAttributionSource(), this,
+ context.getPackageManager());
mBinderService = new GrammaticalInflectionBinderService();
mPermissionManager = context.getSystemService(PermissionManager.class);
}
@@ -176,6 +177,18 @@ public class GrammaticalInflectionService extends SystemService {
}
@Override
+ @Nullable
+ public byte[] getSystemBackupPayload(int userId) {
+ isCallerAllowed();
+ return mBackupHelper.getSystemBackupPayload(userId);
+ }
+
+ @Override
+ public void applyRestoredSystemPayload(byte[] payload, int userId) {
+ mBackupHelper.applyRestoredSystemPayload(payload, userId);
+ }
+
+ @Override
public int getSystemGrammaticalGender(int userId) {
return checkSystemTermsOfAddressIsEnabled()
? GrammaticalInflectionService.this.getSystemGrammaticalGender(
@@ -290,6 +303,7 @@ public class GrammaticalInflectionService extends SystemService {
userId,
grammaticalGender != GRAMMATICAL_GENDER_NOT_SPECIFIED,
preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED);
+ GrammaticalInflectionBackupHelper.notifyBackupManager();
} catch (RemoteException e) {
Log.w(TAG, "Can not update configuration", e);
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 77119d5ac384..f32c11d90c0d 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1197,54 +1197,11 @@ public class InputManagerService extends IInputManager.Stub
}
@Override // Binder call
- public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
- final InputDeviceIdentifier identifier) {
- return mKeyboardLayoutManager.getKeyboardLayoutsForInputDevice(identifier);
- }
-
- @Override // Binder call
public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
return mKeyboardLayoutManager.getKeyboardLayout(keyboardLayoutDescriptor);
}
@Override // Binder call
- public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
- return mKeyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(identifier);
- }
-
- @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
- @Override // Binder call
- public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- super.setCurrentKeyboardLayoutForInputDevice_enforcePermission();
- mKeyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- }
-
- @Override // Binder call
- public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
- return mKeyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(identifier);
- }
-
- @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
- @Override // Binder call
- public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- super.addKeyboardLayoutForInputDevice_enforcePermission();
- mKeyboardLayoutManager.addKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- }
-
- @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
- @Override // Binder call
- public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- super.removeKeyboardLayoutForInputDevice_enforcePermission();
- mKeyboardLayoutManager.removeKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- }
-
- @Override // Binder call
public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
InputDeviceIdentifier identifier, @UserIdInt int userId,
@NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
@@ -1270,11 +1227,6 @@ public class InputManagerService extends IInputManager.Stub
imeInfo, imeSubtype);
}
-
- public void switchKeyboardLayout(int deviceId, int direction) {
- mKeyboardLayoutManager.switchKeyboardLayout(deviceId, direction);
- }
-
public void setFocusedApplication(int displayId, InputApplicationHandle application) {
mNative.setFocusedApplication(displayId, application);
}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 661008103a25..9ba647fb1d2d 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -60,7 +60,6 @@ import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -69,7 +68,6 @@ import android.view.KeyCharacterMap;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
-import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -96,7 +94,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.stream.Stream;
/**
* A component of {@link InputManagerService} responsible for managing Physical Keyboard layouts.
@@ -112,9 +109,8 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int MSG_UPDATE_EXISTING_DEVICES = 1;
- private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
- private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
- private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
+ private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 2;
+ private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 3;
private final Context mContext;
private final NativeInputManagerService mNative;
@@ -126,13 +122,14 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
// Connected keyboards with associated keyboard layouts (either auto-detected or manually
// selected layout).
private final SparseArray<KeyboardConfiguration> mConfiguredKeyboards = new SparseArray<>();
- private Toast mSwitchedKeyboardLayoutToast;
// This cache stores "best-matched" layouts so that we don't need to run the matching
// algorithm repeatedly.
@GuardedBy("mKeyboardLayoutCache")
private final Map<String, KeyboardLayoutSelectionResult> mKeyboardLayoutCache =
new ArrayMap<>();
+
+ private HashSet<String> mAvailableLayouts = new HashSet<>();
private final Object mImeInfoLock = new Object();
@Nullable
@GuardedBy("mImeInfoLock")
@@ -206,68 +203,51 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
boolean needToShowNotification = false;
- if (!useNewSettingsUi()) {
- synchronized (mDataStore) {
- String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
- if (layout == null) {
- layout = getDefaultKeyboardLayout(inputDevice);
- if (layout != null) {
- setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
- }
- }
- if (layout == null) {
- // In old settings show notification always until user manually selects a
- // layout in the settings.
- needToShowNotification = true;
- }
- }
- } else {
- Set<String> selectedLayouts = new HashSet<>();
- List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
- List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>();
- boolean hasMissingLayout = false;
- for (ImeInfo imeInfo : imeInfoList) {
- // Check if the layout has been previously configured
- KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
- keyboardIdentifier, imeInfo);
- boolean noLayoutFound = result.getLayoutDescriptor() == null;
- if (!noLayoutFound) {
- selectedLayouts.add(result.getLayoutDescriptor());
- }
- resultList.add(result);
- hasMissingLayout |= noLayoutFound;
+ Set<String> selectedLayouts = new HashSet<>();
+ List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
+ List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>();
+ boolean hasMissingLayout = false;
+ for (ImeInfo imeInfo : imeInfoList) {
+ // Check if the layout has been previously configured
+ KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
+ keyboardIdentifier, imeInfo);
+ if (result.getLayoutDescriptor() != null) {
+ selectedLayouts.add(result.getLayoutDescriptor());
+ } else {
+ hasMissingLayout = true;
}
+ resultList.add(result);
+ }
- if (DEBUG) {
- Slog.d(TAG,
- "Layouts selected for input device: " + keyboardIdentifier
- + " -> selectedLayouts: " + selectedLayouts);
- }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Layouts selected for input device: " + keyboardIdentifier
+ + " -> selectedLayouts: " + selectedLayouts);
+ }
- // If even one layout not configured properly, we need to ask user to configure
- // the keyboard properly from the Settings.
- if (hasMissingLayout) {
- selectedLayouts.clear();
- }
+ // If even one layout not configured properly, we need to ask user to configure
+ // the keyboard properly from the Settings.
+ if (hasMissingLayout) {
+ selectedLayouts.clear();
+ }
- config.setConfiguredLayouts(selectedLayouts);
+ config.setConfiguredLayouts(selectedLayouts);
- synchronized (mDataStore) {
- try {
- final String key = keyboardIdentifier.toString();
- if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
- // Need to show the notification only if layout selection changed
- // from the previous configuration
- needToShowNotification = true;
- }
+ synchronized (mDataStore) {
+ try {
+ final String key = keyboardIdentifier.toString();
+ if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
+ // Need to show the notification only if layout selection changed
+ // from the previous configuration
+ needToShowNotification = true;
+ }
- if (shouldLogConfiguration) {
- logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList,
- !mDataStore.hasInputDeviceEntry(key));
- }
- } finally {
- mDataStore.saveIfNeeded();
+ if (shouldLogConfiguration) {
+ logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList,
+ !mDataStore.hasInputDeviceEntry(key));
}
+ } finally {
+ mDataStore.saveIfNeeded();
}
}
if (needToShowNotification) {
@@ -275,63 +255,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
- private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
- final Locale systemLocale = mContext.getResources().getConfiguration().locale;
- // If our locale doesn't have a language for some reason, then we don't really have a
- // reasonable default.
- if (TextUtils.isEmpty(systemLocale.getLanguage())) {
- return null;
- }
- final List<KeyboardLayout> layouts = new ArrayList<>();
- visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> {
- // Only select a default when we know the layout is appropriate. For now, this
- // means it's a custom layout for a specific keyboard.
- if (layout.getVendorId() != inputDevice.getVendorId()
- || layout.getProductId() != inputDevice.getProductId()) {
- return;
- }
- final LocaleList locales = layout.getLocales();
- for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
- final Locale locale = locales.get(localeIndex);
- if (locale != null && isCompatibleLocale(systemLocale, locale)) {
- layouts.add(layout);
- break;
- }
- }
- });
-
- if (layouts.isEmpty()) {
- return null;
- }
-
- // First sort so that ones with higher priority are listed at the top
- Collections.sort(layouts);
- // Next we want to try to find an exact match of language, country and variant.
- for (KeyboardLayout layout : layouts) {
- final LocaleList locales = layout.getLocales();
- for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
- final Locale locale = locales.get(localeIndex);
- if (locale != null && locale.getCountry().equals(systemLocale.getCountry())
- && locale.getVariant().equals(systemLocale.getVariant())) {
- return layout.getDescriptor();
- }
- }
- }
- // Then try an exact match of language and country
- for (KeyboardLayout layout : layouts) {
- final LocaleList locales = layout.getLocales();
- for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
- final Locale locale = locales.get(localeIndex);
- if (locale != null && locale.getCountry().equals(systemLocale.getCountry())) {
- return layout.getDescriptor();
- }
- }
- }
-
- // Give up and just use the highest priority layout with matching language
- return layouts.get(0).getDescriptor();
- }
-
private static boolean isCompatibleLocale(Locale systemLocale, Locale keyboardLocale) {
// Different languages are never compatible
if (!systemLocale.getLanguage().equals(keyboardLocale.getLanguage())) {
@@ -343,11 +266,19 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
|| systemLocale.getCountry().equals(keyboardLocale.getCountry());
}
+ @MainThread
private void updateKeyboardLayouts() {
// Scan all input devices state for keyboard layouts that have been uninstalled.
- final HashSet<String> availableKeyboardLayouts = new HashSet<String>();
+ final HashSet<String> availableKeyboardLayouts = new HashSet<>();
visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) ->
availableKeyboardLayouts.add(layout.getDescriptor()));
+
+ // If available layouts don't change, there is no need to reload layouts.
+ if (mAvailableLayouts.equals(availableKeyboardLayouts)) {
+ return;
+ }
+ mAvailableLayouts = availableKeyboardLayouts;
+
synchronized (mDataStore) {
try {
mDataStore.removeUninstalledKeyboardLayouts(availableKeyboardLayouts);
@@ -374,53 +305,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
@AnyThread
- public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
- final InputDeviceIdentifier identifier) {
- if (useNewSettingsUi()) {
- // Provide all supported keyboard layouts since Ime info is not provided
- return getKeyboardLayouts();
- }
- final String[] enabledLayoutDescriptors =
- getEnabledKeyboardLayoutsForInputDevice(identifier);
- final ArrayList<KeyboardLayout> enabledLayouts =
- new ArrayList<>(enabledLayoutDescriptors.length);
- final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
- visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
- boolean mHasSeenDeviceSpecificLayout;
-
- @Override
- public void visitKeyboardLayout(Resources resources,
- int keyboardLayoutResId, KeyboardLayout layout) {
- // First check if it's enabled. If the keyboard layout is enabled then we always
- // want to return it as a possible layout for the device.
- for (String s : enabledLayoutDescriptors) {
- if (s != null && s.equals(layout.getDescriptor())) {
- enabledLayouts.add(layout);
- return;
- }
- }
- // Next find any potential layouts that aren't yet enabled for the device. For
- // devices that have special layouts we assume there's a reason that the generic
- // layouts don't work for them so we don't want to return them since it's likely
- // to result in a poor user experience.
- if (layout.getVendorId() == identifier.getVendorId()
- && layout.getProductId() == identifier.getProductId()) {
- if (!mHasSeenDeviceSpecificLayout) {
- mHasSeenDeviceSpecificLayout = true;
- potentialLayouts.clear();
- }
- potentialLayouts.add(layout);
- } else if (layout.getVendorId() == -1 && layout.getProductId() == -1
- && !mHasSeenDeviceSpecificLayout) {
- potentialLayouts.add(layout);
- }
- }
- });
- return Stream.concat(enabledLayouts.stream(), potentialLayouts.stream()).toArray(
- KeyboardLayout[]::new);
- }
-
- @AnyThread
@Nullable
public KeyboardLayout getKeyboardLayout(@NonNull String keyboardLayoutDescriptor) {
Objects.requireNonNull(keyboardLayoutDescriptor,
@@ -580,195 +464,16 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
return LocaleList.forLanguageTags(languageTags.replace('|', ','));
}
- @AnyThread
- @Nullable
- public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "getCurrentKeyboardLayoutForInputDevice API not supported");
- return null;
- }
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- String layout;
- // try loading it using the layout descriptor if we have it
- layout = mDataStore.getCurrentKeyboardLayout(key);
- if (layout == null && !key.equals(identifier.getDescriptor())) {
- // if it doesn't exist fall back to the device descriptor
- layout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
- }
- if (DEBUG) {
- Slog.d(TAG, "getCurrentKeyboardLayoutForInputDevice() "
- + identifier.toString() + ": " + layout);
- }
- return layout;
- }
- }
-
- @AnyThread
- public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "setCurrentKeyboardLayoutForInputDevice API not supported");
- return;
- }
-
- Objects.requireNonNull(keyboardLayoutDescriptor,
- "keyboardLayoutDescriptor must not be null");
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- try {
- if (mDataStore.setCurrentKeyboardLayout(key, keyboardLayoutDescriptor)) {
- if (DEBUG) {
- Slog.d(TAG, "setCurrentKeyboardLayoutForInputDevice() " + identifier
- + " key: " + key
- + " keyboardLayoutDescriptor: " + keyboardLayoutDescriptor);
- }
- mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
- }
- } finally {
- mDataStore.saveIfNeeded();
- }
- }
- }
-
- @AnyThread
- public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "getEnabledKeyboardLayoutsForInputDevice API not supported");
- return new String[0];
- }
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- String[] layouts = mDataStore.getKeyboardLayouts(key);
- if ((layouts == null || layouts.length == 0)
- && !key.equals(identifier.getDescriptor())) {
- layouts = mDataStore.getKeyboardLayouts(identifier.getDescriptor());
- }
- return layouts;
- }
- }
-
- @AnyThread
- public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "addKeyboardLayoutForInputDevice API not supported");
- return;
- }
- Objects.requireNonNull(keyboardLayoutDescriptor,
- "keyboardLayoutDescriptor must not be null");
-
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- try {
- String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
- if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
- oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
- }
- if (mDataStore.addKeyboardLayout(key, keyboardLayoutDescriptor)
- && !Objects.equals(oldLayout,
- mDataStore.getCurrentKeyboardLayout(key))) {
- mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
- }
- } finally {
- mDataStore.saveIfNeeded();
- }
- }
- }
-
- @AnyThread
- public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "removeKeyboardLayoutForInputDevice API not supported");
- return;
- }
- Objects.requireNonNull(keyboardLayoutDescriptor,
- "keyboardLayoutDescriptor must not be null");
-
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- try {
- String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
- if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
- oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
- }
- boolean removed = mDataStore.removeKeyboardLayout(key, keyboardLayoutDescriptor);
- if (!key.equals(identifier.getDescriptor())) {
- // We need to remove from both places to ensure it is gone
- removed |= mDataStore.removeKeyboardLayout(identifier.getDescriptor(),
- keyboardLayoutDescriptor);
- }
- if (removed && !Objects.equals(oldLayout,
- mDataStore.getCurrentKeyboardLayout(key))) {
- mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
- }
- } finally {
- mDataStore.saveIfNeeded();
- }
- }
- }
-
- @AnyThread
- public void switchKeyboardLayout(int deviceId, int direction) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "switchKeyboardLayout API not supported");
- return;
- }
- mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
- }
-
- @MainThread
- private void handleSwitchKeyboardLayout(int deviceId, int direction) {
- final InputDevice device = getInputDevice(deviceId);
- if (device != null) {
- final boolean changed;
- final String keyboardLayoutDescriptor;
-
- String key = new KeyboardIdentifier(device.getIdentifier()).toString();
- synchronized (mDataStore) {
- try {
- changed = mDataStore.switchKeyboardLayout(key, direction);
- keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
- key);
- } finally {
- mDataStore.saveIfNeeded();
- }
- }
-
- if (changed) {
- if (mSwitchedKeyboardLayoutToast != null) {
- mSwitchedKeyboardLayoutToast.cancel();
- mSwitchedKeyboardLayoutToast = null;
- }
- if (keyboardLayoutDescriptor != null) {
- KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
- if (keyboardLayout != null) {
- mSwitchedKeyboardLayoutToast = Toast.makeText(
- mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
- mSwitchedKeyboardLayoutToast.show();
- }
- }
-
- reloadKeyboardLayouts();
- }
- }
- }
-
@Nullable
@AnyThread
public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier, String languageTag,
String layoutType) {
String keyboardLayoutDescriptor;
- if (useNewSettingsUi()) {
- synchronized (mImeInfoLock) {
- KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
- new KeyboardIdentifier(identifier, languageTag, layoutType),
- mCurrentImeInfo);
- keyboardLayoutDescriptor = result.getLayoutDescriptor();
- }
- } else {
- keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
+ synchronized (mImeInfoLock) {
+ KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
+ new KeyboardIdentifier(identifier, languageTag, layoutType),
+ mCurrentImeInfo);
+ keyboardLayoutDescriptor = result.getLayoutDescriptor();
}
if (keyboardLayoutDescriptor == null) {
return null;
@@ -797,10 +502,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
InputDeviceIdentifier identifier, @UserIdInt int userId,
@NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
- if (!useNewSettingsUi()) {
- Slog.e(TAG, "getKeyboardLayoutForInputDevice() API not supported");
- return FAILED;
- }
InputDevice inputDevice = getInputDevice(identifier);
if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
return FAILED;
@@ -820,10 +521,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
@UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
@Nullable InputMethodSubtype imeSubtype,
String keyboardLayoutDescriptor) {
- if (!useNewSettingsUi()) {
- Slog.e(TAG, "setKeyboardLayoutForInputDevice() API not supported");
- return;
- }
Objects.requireNonNull(keyboardLayoutDescriptor,
"keyboardLayoutDescriptor must not be null");
InputDevice inputDevice = getInputDevice(identifier);
@@ -854,10 +551,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
@UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
@Nullable InputMethodSubtype imeSubtype) {
- if (!useNewSettingsUi()) {
- Slog.e(TAG, "getKeyboardLayoutListForInputDevice() API not supported");
- return new KeyboardLayout[0];
- }
InputDevice inputDevice = getInputDevice(identifier);
if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
return new KeyboardLayout[0];
@@ -923,10 +616,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
public void onInputMethodSubtypeChanged(@UserIdInt int userId,
@Nullable InputMethodSubtypeHandle subtypeHandle,
@Nullable InputMethodSubtype subtype) {
- if (!useNewSettingsUi()) {
- Slog.e(TAG, "onInputMethodSubtypeChanged() API not supported");
- return;
- }
if (subtypeHandle == null) {
if (DEBUG) {
Slog.d(TAG, "No InputMethod is running, ignoring change");
@@ -1289,9 +978,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
onInputDeviceAdded(deviceId);
}
return true;
- case MSG_SWITCH_KEYBOARD_LAYOUT:
- handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
- return true;
case MSG_RELOAD_KEYBOARD_LAYOUTS:
reloadKeyboardLayouts();
return true;
@@ -1303,10 +989,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
- private boolean useNewSettingsUi() {
- return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI);
- }
-
@Nullable
private InputDevice getInputDevice(int deviceId) {
InputManager inputManager = mContext.getSystemService(InputManager.class);
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index 31083fd5de03..7859253d66e0 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -27,7 +27,6 @@ import android.util.Xml;
import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -42,7 +41,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -74,7 +72,7 @@ final class PersistentDataStore {
new HashMap<String, InputDeviceState>();
// The interface for methods which should be replaced by the test harness.
- private Injector mInjector;
+ private final Injector mInjector;
// True if the data has been loaded.
private boolean mLoaded;
@@ -83,7 +81,7 @@ final class PersistentDataStore {
private boolean mDirty;
// Storing key remapping
- private Map<Integer, Integer> mKeyRemapping = new HashMap<>();
+ private final Map<Integer, Integer> mKeyRemapping = new HashMap<>();
public PersistentDataStore() {
this(new Injector());
@@ -130,22 +128,6 @@ final class PersistentDataStore {
}
@Nullable
- public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
- InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
- return state != null ? state.getCurrentKeyboardLayout() : null;
- }
-
- public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor,
- String keyboardLayoutDescriptor) {
- InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
- if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) {
- setDirty();
- return true;
- }
- return false;
- }
-
- @Nullable
public String getKeyboardLayout(String inputDeviceDescriptor, String key) {
InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
return state != null ? state.getKeyboardLayout(key) : null;
@@ -171,43 +153,6 @@ final class PersistentDataStore {
return false;
}
- public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
- InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
- if (state == null) {
- return (String[])ArrayUtils.emptyArray(String.class);
- }
- return state.getKeyboardLayouts();
- }
-
- public boolean addKeyboardLayout(String inputDeviceDescriptor,
- String keyboardLayoutDescriptor) {
- InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
- if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
- setDirty();
- return true;
- }
- return false;
- }
-
- public boolean removeKeyboardLayout(String inputDeviceDescriptor,
- String keyboardLayoutDescriptor) {
- InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
- if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) {
- setDirty();
- return true;
- }
- return false;
- }
-
- public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) {
- InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
- if (state != null && state.switchKeyboardLayout(direction)) {
- setDirty();
- return true;
- }
- return false;
- }
-
public boolean setKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId,
int brightness) {
InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
@@ -417,9 +362,6 @@ final class PersistentDataStore {
"x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
private final TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
- @Nullable
- private String mCurrentKeyboardLayout;
- private final ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
private final SparseIntArray mKeyboardBacklightBrightnessMap = new SparseIntArray();
private final Map<String, String> mKeyboardLayoutMap = new ArrayMap<>();
@@ -465,49 +407,6 @@ final class PersistentDataStore {
return true;
}
- @Nullable
- public String getCurrentKeyboardLayout() {
- return mCurrentKeyboardLayout;
- }
-
- public boolean setCurrentKeyboardLayout(String keyboardLayout) {
- if (Objects.equals(mCurrentKeyboardLayout, keyboardLayout)) {
- return false;
- }
- addKeyboardLayout(keyboardLayout);
- mCurrentKeyboardLayout = keyboardLayout;
- return true;
- }
-
- public String[] getKeyboardLayouts() {
- if (mKeyboardLayouts.isEmpty()) {
- return (String[])ArrayUtils.emptyArray(String.class);
- }
- return mKeyboardLayouts.toArray(new String[mKeyboardLayouts.size()]);
- }
-
- public boolean addKeyboardLayout(String keyboardLayout) {
- int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
- if (index >= 0) {
- return false;
- }
- mKeyboardLayouts.add(-index - 1, keyboardLayout);
- if (mCurrentKeyboardLayout == null) {
- mCurrentKeyboardLayout = keyboardLayout;
- }
- return true;
- }
-
- public boolean removeKeyboardLayout(String keyboardLayout) {
- int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
- if (index < 0) {
- return false;
- }
- mKeyboardLayouts.remove(index);
- updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index);
- return true;
- }
-
public boolean setKeyboardBacklightBrightness(int lightId, int brightness) {
if (mKeyboardBacklightBrightnessMap.get(lightId, INVALID_VALUE) == brightness) {
return false;
@@ -521,48 +420,8 @@ final class PersistentDataStore {
return brightness == INVALID_VALUE ? OptionalInt.empty() : OptionalInt.of(brightness);
}
- private void updateCurrentKeyboardLayoutIfRemoved(
- String removedKeyboardLayout, int removedIndex) {
- if (Objects.equals(mCurrentKeyboardLayout, removedKeyboardLayout)) {
- if (!mKeyboardLayouts.isEmpty()) {
- int index = removedIndex;
- if (index == mKeyboardLayouts.size()) {
- index = 0;
- }
- mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
- } else {
- mCurrentKeyboardLayout = null;
- }
- }
- }
-
- public boolean switchKeyboardLayout(int direction) {
- final int size = mKeyboardLayouts.size();
- if (size < 2) {
- return false;
- }
- int index = Collections.binarySearch(mKeyboardLayouts, mCurrentKeyboardLayout);
- assert index >= 0;
- if (direction > 0) {
- index = (index + 1) % size;
- } else {
- index = (index + size - 1) % size;
- }
- mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
- return true;
- }
-
public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
boolean changed = false;
- for (int i = mKeyboardLayouts.size(); i-- > 0; ) {
- String keyboardLayout = mKeyboardLayouts.get(i);
- if (!availableKeyboardLayouts.contains(keyboardLayout)) {
- Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout);
- mKeyboardLayouts.remove(i);
- updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i);
- changed = true;
- }
- }
List<String> removedEntries = new ArrayList<>();
for (String key : mKeyboardLayoutMap.keySet()) {
if (!availableKeyboardLayouts.contains(mKeyboardLayoutMap.get(key))) {
@@ -582,27 +441,7 @@ final class PersistentDataStore {
throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- if (parser.getName().equals("keyboard-layout")) {
- String descriptor = parser.getAttributeValue(null, "descriptor");
- if (descriptor == null) {
- throw new XmlPullParserException(
- "Missing descriptor attribute on keyboard-layout.");
- }
- String current = parser.getAttributeValue(null, "current");
- if (mKeyboardLayouts.contains(descriptor)) {
- throw new XmlPullParserException(
- "Found duplicate keyboard layout.");
- }
-
- mKeyboardLayouts.add(descriptor);
- if (current != null && current.equals("true")) {
- if (mCurrentKeyboardLayout != null) {
- throw new XmlPullParserException(
- "Found multiple current keyboard layouts.");
- }
- mCurrentKeyboardLayout = descriptor;
- }
- } else if (parser.getName().equals("keyed-keyboard-layout")) {
+ if (parser.getName().equals("keyed-keyboard-layout")) {
String key = parser.getAttributeValue(null, "key");
if (key == null) {
throw new XmlPullParserException(
@@ -676,27 +515,9 @@ final class PersistentDataStore {
}
}
}
-
- // Maintain invariant that layouts are sorted.
- Collections.sort(mKeyboardLayouts);
-
- // Maintain invariant that there is always a current keyboard layout unless
- // there are none installed.
- if (mCurrentKeyboardLayout == null && !mKeyboardLayouts.isEmpty()) {
- mCurrentKeyboardLayout = mKeyboardLayouts.get(0);
- }
}
public void saveToXml(TypedXmlSerializer serializer) throws IOException {
- for (String layout : mKeyboardLayouts) {
- serializer.startTag(null, "keyboard-layout");
- serializer.attribute(null, "descriptor", layout);
- if (layout.equals(mCurrentKeyboardLayout)) {
- serializer.attributeBoolean(null, "current", true);
- }
- serializer.endTag(null, "keyboard-layout");
- }
-
for (String key : mKeyboardLayoutMap.keySet()) {
serializer.startTag(null, "keyed-keyboard-layout");
serializer.attribute(null, "key", key);
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
new file mode 100644
index 000000000000..7890fe0ed461
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
@@ -0,0 +1,464 @@
+/*
+ * 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.inputmethod;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+import android.annotation.BinderThread;
+import android.annotation.EnforcePermission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IBooleanListener;
+import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
+import com.android.internal.inputmethod.IImeTracker;
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+import com.android.internal.view.IInputMethodManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.List;
+
+/**
+ * An actual implementation class of {@link IInputMethodManager.Stub} to allow other classes to
+ * focus on handling IPC callbacks.
+ */
+final class IInputMethodManagerImpl extends IInputMethodManager.Stub {
+
+ /**
+ * Tells that the given permission is already verified before the annotated method gets called.
+ */
+ @Retention(SOURCE)
+ @Target({METHOD})
+ @interface PermissionVerified {
+ String value() default "";
+ }
+
+ @BinderThread
+ interface Callback {
+ void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection,
+ int selfReportedDisplayId);
+
+ InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId);
+
+ List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
+ @DirectBootAwareness int directBootAwareness);
+
+ List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId);
+
+ List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
+ boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId);
+
+ InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId);
+
+ boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+ @MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason);
+
+ boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason);
+
+ @PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+ void hideSoftInputFromServerForTest();
+
+ void startInputOrWindowGainedFocusAsync(
+ @StartInputReason int startInputReason, IInputMethodClient client,
+ IBinder windowToken, @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
+ @Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq);
+
+ InputBindResult startInputOrWindowGainedFocus(
+ @StartInputReason int startInputReason, IInputMethodClient client,
+ IBinder windowToken, @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
+ @Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher);
+
+ void showInputMethodPickerFromClient(IInputMethodClient client, int auxiliarySubtypeMode);
+
+ @PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId);
+
+ @PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+ boolean isInputMethodPickerShownForTest();
+
+ InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId);
+
+ void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
+ @UserIdInt int userId);
+
+ void setExplicitlyEnabledInputMethodSubtypes(String imeId,
+ @NonNull int[] subtypeHashCodes, @UserIdInt int userId);
+
+ int getInputMethodWindowVisibleHeight(IInputMethodClient client);
+
+ void reportPerceptibleAsync(IBinder windowToken, boolean perceptible);
+
+ @PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ void removeImeSurface();
+
+ void removeImeSurfaceFromWindowAsync(IBinder windowToken);
+
+ void startProtoDump(byte[] bytes, int i, String s);
+
+ boolean isImeTraceEnabled();
+
+ @PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
+ void startImeTrace();
+
+ @PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
+ void stopImeTrace();
+
+ void startStylusHandwriting(IInputMethodClient client);
+
+ void startConnectionlessStylusHandwriting(IInputMethodClient client, @UserIdInt int userId,
+ @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName,
+ @Nullable String delegatorPackageName,
+ @NonNull IConnectionlessHandwritingCallback callback);
+
+ boolean acceptStylusHandwritingDelegation(@NonNull IInputMethodClient client,
+ @UserIdInt int userId, @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags);
+
+ void acceptStylusHandwritingDelegationAsync(@NonNull IInputMethodClient client,
+ @UserIdInt int userId, @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback);
+
+ void prepareStylusHandwritingDelegation(@NonNull IInputMethodClient client,
+ @UserIdInt int userId, @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName);
+
+ boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId, boolean connectionless);
+
+ @PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+ void addVirtualStylusIdForTestSession(IInputMethodClient client);
+
+ @PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+ void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout);
+
+ IImeTracker getImeTrackerService();
+
+ void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err, @NonNull String[] args,
+ @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver,
+ @NonNull Binder self);
+
+ void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args);
+ }
+
+ @NonNull
+ private final Callback mCallback;
+
+ private IInputMethodManagerImpl(@NonNull Callback callback) {
+ mCallback = callback;
+ }
+
+ static IInputMethodManagerImpl create(@NonNull Callback callback) {
+ return new IInputMethodManagerImpl(callback);
+ }
+
+ @Override
+ public void addClient(IInputMethodClient client, IRemoteInputConnection inputmethod,
+ int untrustedDisplayId) {
+ mCallback.addClient(client, inputmethod, untrustedDisplayId);
+ }
+
+ @Override
+ public InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) {
+ return mCallback.getCurrentInputMethodInfoAsUser(userId);
+ }
+
+ @Override
+ public List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
+ int directBootAwareness) {
+ return mCallback.getInputMethodList(userId, directBootAwareness);
+ }
+
+ @Override
+ public List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) {
+ return mCallback.getEnabledInputMethodList(userId);
+ }
+
+ @Override
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
+ boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
+ return mCallback.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes,
+ userId);
+ }
+
+ @Override
+ public InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId) {
+ return mCallback.getLastInputMethodSubtype(userId);
+ }
+
+ @Override
+ public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+ @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+ @MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason) {
+ return mCallback.showSoftInput(client, windowToken, statsToken, flags, lastClickToolType,
+ resultReceiver, reason);
+ }
+
+ @Override
+ public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+ @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ return mCallback.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver,
+ reason);
+ }
+
+ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @Override
+ public void hideSoftInputFromServerForTest() {
+ super.hideSoftInputFromServerForTest_enforcePermission();
+
+ mCallback.hideSoftInputFromServerForTest();
+ }
+
+ @Override
+ public InputBindResult startInputOrWindowGainedFocus(
+ @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ int windowFlags, @Nullable EditorInfo editorInfo,
+ IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ return mCallback.startInputOrWindowGainedFocus(
+ startInputReason, client, windowToken, startInputFlags, softInputMode,
+ windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection,
+ unverifiedTargetSdkVersion, userId, imeDispatcher);
+ }
+
+ @Override
+ public void startInputOrWindowGainedFocusAsync(@StartInputReason int startInputReason,
+ IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ int windowFlags, @Nullable EditorInfo editorInfo,
+ IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq) {
+ mCallback.startInputOrWindowGainedFocusAsync(
+ startInputReason, client, windowToken, startInputFlags, softInputMode,
+ windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection,
+ unverifiedTargetSdkVersion, userId, imeDispatcher, startInputSeq);
+ }
+
+ @Override
+ public void showInputMethodPickerFromClient(IInputMethodClient client,
+ int auxiliarySubtypeMode) {
+ mCallback.showInputMethodPickerFromClient(client, auxiliarySubtypeMode);
+ }
+
+ @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @Override
+ public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
+ super.showInputMethodPickerFromSystem_enforcePermission();
+
+ mCallback.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
+
+ }
+
+ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @Override
+ public boolean isInputMethodPickerShownForTest() {
+ super.isInputMethodPickerShownForTest_enforcePermission();
+
+ return mCallback.isInputMethodPickerShownForTest();
+ }
+
+ @Override
+ public InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
+ return mCallback.getCurrentInputMethodSubtype(userId);
+ }
+
+ @Override
+ public void setAdditionalInputMethodSubtypes(String id, InputMethodSubtype[] subtypes,
+ @UserIdInt int userId) {
+ mCallback.setAdditionalInputMethodSubtypes(id, subtypes, userId);
+ }
+
+ @Override
+ public void setExplicitlyEnabledInputMethodSubtypes(String imeId, int[] subtypeHashCodes,
+ @UserIdInt int userId) {
+ mCallback.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId);
+ }
+
+ @Override
+ public int getInputMethodWindowVisibleHeight(IInputMethodClient client) {
+ return mCallback.getInputMethodWindowVisibleHeight(client);
+ }
+
+ @Override
+ public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
+ mCallback.reportPerceptibleAsync(windowToken, perceptible);
+ }
+
+ @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @Override
+ public void removeImeSurface() {
+ super.removeImeSurface_enforcePermission();
+
+ mCallback.removeImeSurface();
+ }
+
+ @Override
+ public void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
+ mCallback.removeImeSurfaceFromWindowAsync(windowToken);
+ }
+
+ @Override
+ public void startProtoDump(byte[] protoDump, int source, String where) {
+ mCallback.startProtoDump(protoDump, source, where);
+ }
+
+ @Override
+ public boolean isImeTraceEnabled() {
+ return mCallback.isImeTraceEnabled();
+ }
+
+ @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @Override
+ public void startImeTrace() {
+ super.startImeTrace_enforcePermission();
+
+ mCallback.startImeTrace();
+ }
+
+ @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @Override
+ public void stopImeTrace() {
+ super.stopImeTrace_enforcePermission();
+
+ mCallback.stopImeTrace();
+ }
+
+ @Override
+ public void startStylusHandwriting(IInputMethodClient client) {
+ mCallback.startStylusHandwriting(client);
+ }
+
+ @Override
+ public void startConnectionlessStylusHandwriting(IInputMethodClient client,
+ @UserIdInt int userId, CursorAnchorInfo cursorAnchorInfo,
+ String delegatePackageName, String delegatorPackageName,
+ IConnectionlessHandwritingCallback callback) {
+ mCallback.startConnectionlessStylusHandwriting(client, userId, cursorAnchorInfo,
+ delegatePackageName, delegatorPackageName, callback);
+ }
+
+ @Override
+ public void prepareStylusHandwritingDelegation(IInputMethodClient client, @UserIdInt int userId,
+ String delegatePackageName, String delegatorPackageName) {
+ mCallback.prepareStylusHandwritingDelegation(client, userId,
+ delegatePackageName, delegatorPackageName);
+ }
+
+ @Override
+ public boolean acceptStylusHandwritingDelegation(IInputMethodClient client,
+ @UserIdInt int userId, String delegatePackageName, String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags) {
+ return mCallback.acceptStylusHandwritingDelegation(client, userId,
+ delegatePackageName, delegatorPackageName, flags);
+ }
+
+ @Override
+ public void acceptStylusHandwritingDelegationAsync(IInputMethodClient client,
+ @UserIdInt int userId, String delegatePackageName, String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags,
+ IBooleanListener callback) {
+ mCallback.acceptStylusHandwritingDelegationAsync(client, userId,
+ delegatePackageName, delegatorPackageName, flags, callback);
+ }
+
+ @Override
+ public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId,
+ boolean connectionless) {
+ return mCallback.isStylusHandwritingAvailableAsUser(userId, connectionless);
+ }
+
+ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @Override
+ public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
+ super.addVirtualStylusIdForTestSession_enforcePermission();
+
+ mCallback.addVirtualStylusIdForTestSession(client);
+ }
+
+ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @Override
+ public void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout) {
+ super.setStylusWindowIdleTimeoutForTest_enforcePermission();
+
+ mCallback.setStylusWindowIdleTimeoutForTest(client, timeout);
+ }
+
+ @Override
+ public IImeTracker getImeTrackerService() {
+ return mCallback.getImeTrackerService();
+ }
+
+ @Override
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) {
+ mCallback.onShellCommand(in, out, err, args, callback, resultReceiver, this);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mCallback.dump(fd, pw, args);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index a100fe06c407..3e23f972bd45 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -16,10 +16,12 @@
package com.android.server.inputmethod;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -410,7 +412,7 @@ final class InputMethodBindingController {
Slog.v(TAG,
"Removing window token: " + mCurToken + " for display: " + curTokenDisplayId);
}
- mWindowManagerInternal.removeWindowToken(mCurToken, false /* removeWindows */,
+ mWindowManagerInternal.removeWindowToken(mCurToken, true /* removeWindows */,
false /* animateExit */, curTokenDisplayId);
mCurToken = null;
}
@@ -452,9 +454,12 @@ final class InputMethodBindingController {
intent.setComponent(component);
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.input_method_binding_label);
+ var options = ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_DENIED);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS),
- PendingIntent.FLAG_IMMUTABLE));
+ PendingIntent.FLAG_IMMUTABLE, options.toBundle()));
return intent;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c6a48ec9018e..03a85c40ef31 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -61,7 +61,6 @@ import android.annotation.AnyThread;
import android.annotation.BinderThread;
import android.annotation.DrawableRes;
import android.annotation.DurationMillisLong;
-import android.annotation.EnforcePermission;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -172,7 +171,6 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
-import com.android.internal.view.IInputMethodManager;
import com.android.server.AccessibilityManagerInternal;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
@@ -211,8 +209,9 @@ import java.util.function.IntConsumer;
/**
* This class provides a system service that manages input methods.
*/
-public final class InputMethodManagerService extends IInputMethodManager.Stub
- implements Handler.Callback {
+public final class InputMethodManagerService implements IInputMethodManagerImpl.Callback,
+ ZeroJankProxy.Callback, Handler.Callback {
+
// Virtual device id for test.
private static final Integer VIRTUAL_STYLUS_ID_FOR_TEST = 999999;
static final boolean DEBUG = false;
@@ -1236,21 +1235,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@Override
public void onStart() {
mService.publishLocalService();
- IInputMethodManager.Stub service;
+ IInputMethodManagerImpl.Callback service;
if (Flags.useZeroJankProxy()) {
- service =
- new ZeroJankProxy(
- mService.mHandler::post,
- mService,
- () -> {
- synchronized (ImfLock.class) {
- return mService.isInputShown();
- }
- });
+ service = new ZeroJankProxy(mService.mHandler::post, mService);
} else {
service = mService;
}
- publishBinderService(Context.INPUT_METHOD_SERVICE, service, false /*allowIsolated*/,
+ publishBinderService(Context.INPUT_METHOD_SERVICE,
+ IInputMethodManagerImpl.create(service), false /*allowIsolated*/,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
}
@@ -1336,77 +1328,78 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
Context context,
@Nullable ServiceThread serviceThreadForTesting,
@Nullable InputMethodBindingController bindingControllerForTesting) {
- mContext = context;
- mRes = context.getResources();
- SecureSettingsWrapper.onStart(mContext);
- // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
- // additional subtypes in switchUserOnHandlerLocked().
- final ServiceThread thread =
- serviceThreadForTesting != null
- ? serviceThreadForTesting
- : new ServiceThread(
- HANDLER_THREAD_NAME,
- Process.THREAD_PRIORITY_FOREGROUND,
- true /* allowIo */);
- thread.start();
- mHandler = Handler.createAsync(thread.getLooper(), this);
- SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler);
- mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null
- ? serviceThreadForTesting.getLooper() : Looper.getMainLooper());
- // Note: SettingsObserver doesn't register observers in its constructor.
- mSettingsObserver = new SettingsObserver(mHandler);
- mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
- mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
- mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
- mImePlatformCompatUtils = new ImePlatformCompatUtils();
- mInputMethodDeviceConfigs = new InputMethodDeviceConfigs();
- mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
-
- mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
-
- mShowOngoingImeSwitcherForPhones = false;
-
- AdditionalSubtypeMapRepository.initialize(mHandler);
-
- final int userId = mActivityManagerInternal.getCurrentUserId();
-
- // mSettings should be created before buildInputMethodListLocked
- mSettings = InputMethodSettings.createEmptyMap(userId);
-
- mSwitchingController =
- InputMethodSubtypeSwitchingController.createInstanceLocked(context,
- mSettings.getMethodMap(), userId);
- mHardwareKeyboardShortcutController =
- new HardwareKeyboardShortcutController(mSettings.getMethodMap(),
- mSettings.getUserId());
- mMenuController = new InputMethodMenuController(this);
- mBindingController =
- bindingControllerForTesting != null
- ? bindingControllerForTesting
- : new InputMethodBindingController(this);
- mAutofillController = new AutofillSuggestionsController(this);
-
- mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
- mVisibilityApplier = new DefaultImeVisibilityApplier(this);
-
- mClientController = new ClientController(mPackageManagerInternal);
synchronized (ImfLock.class) {
+ mContext = context;
+ mRes = context.getResources();
+ SecureSettingsWrapper.onStart(mContext);
+
+ // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
+ // additional subtypes in switchUserOnHandlerLocked().
+ final ServiceThread thread =
+ serviceThreadForTesting != null
+ ? serviceThreadForTesting
+ : new ServiceThread(
+ HANDLER_THREAD_NAME,
+ Process.THREAD_PRIORITY_FOREGROUND,
+ true /* allowIo */);
+ thread.start();
+ mHandler = Handler.createAsync(thread.getLooper(), this);
+ SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler);
+ mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null
+ ? serviceThreadForTesting.getLooper() : Looper.getMainLooper());
+ // Note: SettingsObserver doesn't register observers in its constructor.
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
+ mImePlatformCompatUtils = new ImePlatformCompatUtils();
+ mInputMethodDeviceConfigs = new InputMethodDeviceConfigs();
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+
+ mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
+
+ mShowOngoingImeSwitcherForPhones = false;
+
+ AdditionalSubtypeMapRepository.initialize(mHandler);
+
+ final int userId = mActivityManagerInternal.getCurrentUserId();
+
+ // mSettings should be created before buildInputMethodListLocked
+ mSettings = InputMethodSettings.createEmptyMap(userId);
+
+ mSwitchingController =
+ InputMethodSubtypeSwitchingController.createInstanceLocked(context,
+ mSettings.getMethodMap(), userId);
+ mHardwareKeyboardShortcutController =
+ new HardwareKeyboardShortcutController(mSettings.getMethodMap(),
+ mSettings.getUserId());
+ mMenuController = new InputMethodMenuController(this);
+ mBindingController =
+ bindingControllerForTesting != null
+ ? bindingControllerForTesting
+ : new InputMethodBindingController(this);
+ mAutofillController = new AutofillSuggestionsController(this);
+
+ mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
+ mVisibilityApplier = new DefaultImeVisibilityApplier(this);
+
+ mClientController = new ClientController(mPackageManagerInternal);
mClientController.addClientControllerCallback(c -> onClientRemoved(c));
mImeBindingState = ImeBindingState.newEmptyState();
- }
- mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
- com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
- mNonPreemptibleInputMethods = mRes.getStringArray(
- com.android.internal.R.array.config_nonPreemptibleInputMethods);
- IntConsumer toolTypeConsumer =
- Flags.useHandwritingListenerForTooltype()
- ? toolType -> onUpdateEditorToolType(toolType) : null;
- Runnable discardDelegationTextRunnable = () -> discardHandwritingDelegationText();
- mHwController = new HandwritingModeController(mContext, thread.getLooper(),
- new InkWindowInitializer(), toolTypeConsumer, discardDelegationTextRunnable);
- registerDeviceListenerAndCheckStylusSupport();
+ mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
+ com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
+ mNonPreemptibleInputMethods = mRes.getStringArray(
+ com.android.internal.R.array.config_nonPreemptibleInputMethods);
+ IntConsumer toolTypeConsumer =
+ Flags.useHandwritingListenerForTooltype()
+ ? toolType -> onUpdateEditorToolType(toolType) : null;
+ Runnable discardDelegationTextRunnable = () -> discardHandwritingDelegationText();
+ mHwController = new HandwritingModeController(mContext, thread.getLooper(),
+ new InkWindowInitializer(), toolTypeConsumer, discardDelegationTextRunnable);
+ registerDeviceListenerAndCheckStylusSupport();
+ }
}
@GuardedBy("ImfLock.class")
@@ -1934,10 +1927,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@Nullable
- ClientState getClientState(IInputMethodClient client) {
- synchronized (ImfLock.class) {
- return mClientController.getClient(client.asBinder());
- }
+ @GuardedBy("ImfLock.class")
+ @Override
+ public ClientState getClientStateLocked(IInputMethodClient client) {
+ return mClientController.getClient(client.asBinder());
}
// TODO(b/314150112): Move this to ClientController.
@@ -2016,7 +2009,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@GuardedBy("ImfLock.class")
- private boolean isInputShown() {
+ @Override
+ public boolean isInputShownLocked() {
return mVisibilityStateComputer.isInputShown();
}
@@ -3132,11 +3126,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
public void startConnectionlessStylusHandwriting(IInputMethodClient client, int userId,
@Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName,
@Nullable String delegatorPackageName,
- @NonNull IConnectionlessHandwritingCallback callback) throws RemoteException {
+ @NonNull IConnectionlessHandwritingCallback callback) {
synchronized (ImfLock.class) {
if (!mBindingController.supportsConnectionlessStylusHandwriting()) {
Slog.w(TAG, "Connectionless stylus handwriting mode unsupported by IME.");
- callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED);
+ try {
+ callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED", e);
+ e.rethrowAsRuntimeException();
+ }
return;
}
}
@@ -3147,7 +3146,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
synchronized (ImfLock.class) {
if (!mClientController.verifyClientAndPackageMatch(client, delegatorPackageName)) {
Slog.w(TAG, "startConnectionlessStylusHandwriting() fail");
- callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);
+ try {
+ callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_OTHER", e);
+ e.rethrowAsRuntimeException();
+ }
throw new IllegalArgumentException("Delegator doesn't match UID");
}
}
@@ -3171,7 +3175,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (!startStylusHandwriting(
client, false, immsCallback, cursorAnchorInfo, isForDelegation)) {
- callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);
+ try {
+ callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_OTHER", e);
+ e.rethrowAsRuntimeException();
+ }
}
}
@@ -3271,11 +3280,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@UserIdInt int userId,
@NonNull String delegatePackageName,
@NonNull String delegatorPackageName,
- @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback)
- throws RemoteException {
+ @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback) {
boolean result = acceptStylusHandwritingDelegation(
client, userId, delegatePackageName, delegatorPackageName, flags);
- callback.onResult(result);
+ try {
+ callback.onResult(result);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report result=" + result, e);
+ e.rethrowAsRuntimeException();
+ }
}
@Override
@@ -3366,7 +3379,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@GuardedBy("ImfLock.class")
boolean showCurrentInputLocked(IBinder windowToken,
@NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
- int lastClickToolType, @Nullable ResultReceiver resultReceiver,
+ @MotionEvent.ToolType int lastClickToolType, @Nullable ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
if (!mVisibilityStateComputer.onImeShowFlags(statsToken, flags)) {
return false;
@@ -3412,7 +3425,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
"InputMethodManagerService#hideSoftInput");
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
- if (isInputShown()) {
+ if (isInputShownLocked()) {
ImeTracker.forLogging().onFailed(
statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
} else {
@@ -3435,10 +3448,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@Override
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
public void hideSoftInputFromServerForTest() {
- super.hideSoftInputFromServerForTest_enforcePermission();
-
synchronized (ImfLock.class) {
hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_SOFT_INPUT);
@@ -3471,7 +3482,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// TODO(b/246309664): Clean up IMMS#mImeWindowVis
IInputMethodInvoker curMethod = getCurMethodLocked();
final boolean shouldHideSoftInput = curMethod != null
- && (isInputShown() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
+ && (isInputShownLocked() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
mVisibilityStateComputer.requestImeVisibility(windowToken, false);
if (shouldHideSoftInput) {
@@ -3844,13 +3855,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
// Always call subtype picker, because subtype picker is a superset of input method
// picker.
- super.showInputMethodPickerFromSystem_enforcePermission();
-
mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
.sendToTarget();
}
@@ -3858,10 +3867,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
/**
* A test API for CTS to make sure that the input method menu is showing.
*/
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
public boolean isInputMethodPickerShownForTest() {
- super.isInputMethodPickerShownForTest_enforcePermission();
-
synchronized (ImfLock.class) {
return mMenuController.isisInputMethodPickerShownForTestLocked();
}
@@ -4161,11 +4168,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
});
}
- @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
@Override
public void removeImeSurface() {
- super.removeImeSurface_enforcePermission();
-
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
}
@@ -4274,11 +4279,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
* a stylus deviceId is not already registered on device.
*/
@BinderThread
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
@Override
public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
- super.addVirtualStylusIdForTestSession_enforcePermission();
-
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession",
@@ -4301,12 +4304,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
* @param timeout to set in milliseconds. To reset to default, use a value <= zero.
*/
@BinderThread
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
@Override
public void setStylusWindowIdleTimeoutForTest(
IInputMethodClient client, @DurationMillisLong long timeout) {
- super.setStylusWindowIdleTimeoutForTest_enforcePermission();
-
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest",
@@ -4402,10 +4403,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@BinderThread
- @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
@Override
public void startImeTrace() {
- super.startImeTrace_enforcePermission();
ImeTracing.getInstance().startTrace(null /* printwriter */);
synchronized (ImfLock.class) {
mClientController.forAllClients(c -> c.mClient.setImeTraceEnabled(true /* enabled */));
@@ -4413,11 +4413,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@BinderThread
- @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
@Override
public void stopImeTrace() {
- super.stopImeTrace_enforcePermission();
-
ImeTracing.getInstance().stopTrace(null /* printwriter */);
synchronized (ImfLock.class) {
mClientController.forAllClients(c -> c.mClient.setImeTraceEnabled(false /* enabled */));
@@ -4697,7 +4695,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// implemented so that auxiliary subtypes will be excluded when the soft
// keyboard is invisible.
synchronized (ImfLock.class) {
- showAuxSubtypes = isInputShown();
+ showAuxSubtypes = isInputShownLocked();
}
break;
case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
@@ -5844,7 +5842,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@BinderThread
@Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
PriorityDump.dump(mPriorityDumper, fd, pw, args);
@@ -5974,7 +5972,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
@Nullable FileDescriptor err,
@NonNull String[] args, @Nullable ShellCallback callback,
- @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ @NonNull ResultReceiver resultReceiver, @NonNull Binder self) {
final int callingUid = Binder.getCallingUid();
// Reject any incoming calls from non-shell users, including ones from the system user.
if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
@@ -5995,7 +5993,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
throw new SecurityException(errorMsg);
}
new ShellCommandImpl(this).exec(
- this, in, out, err, args, callback, resultReceiver);
+ self, in, out, err, args, callback, resultReceiver);
}
private static final class ShellCommandImpl extends ShellCommand {
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 31ce63056864..1cd1ddce78fd 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -36,17 +36,16 @@ import static com.android.server.inputmethod.InputMethodManagerService.TAG;
import android.Manifest;
import android.annotation.BinderThread;
-import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.os.Binder;
import android.os.IBinder;
-import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.util.Slog;
+import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorInfo;
@@ -56,6 +55,7 @@ import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import android.window.ImeOnBackInvokedDispatcher;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.internal.inputmethod.IBooleanListener;
import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
@@ -76,22 +76,25 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
-import java.util.function.BooleanSupplier;
/**
* A proxy that processes all {@link IInputMethodManager} calls asynchronously.
- * @hide
*/
-public class ZeroJankProxy extends IInputMethodManager.Stub {
+final class ZeroJankProxy implements IInputMethodManagerImpl.Callback {
- private final IInputMethodManager mInner;
+ interface Callback extends IInputMethodManagerImpl.Callback {
+ @GuardedBy("ImfLock.class")
+ ClientState getClientStateLocked(IInputMethodClient client);
+ @GuardedBy("ImfLock.class")
+ boolean isInputShownLocked();
+ }
+
+ private final Callback mInner;
private final Executor mExecutor;
- private final BooleanSupplier mIsInputShown;
- ZeroJankProxy(Executor executor, IInputMethodManager inner, BooleanSupplier isInputShown) {
+ ZeroJankProxy(Executor executor, Callback inner) {
mInner = inner;
mExecutor = executor;
- mIsInputShown = isInputShown;
}
private void offload(ThrowingRunnable r) {
@@ -126,45 +129,43 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
@Override
public void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection,
- int selfReportedDisplayId) throws RemoteException {
+ int selfReportedDisplayId) {
offload(() -> mInner.addClient(client, inputConnection, selfReportedDisplayId));
}
@Override
- public InputMethodInfo getCurrentInputMethodInfoAsUser(int userId) throws RemoteException {
+ public InputMethodInfo getCurrentInputMethodInfoAsUser(int userId) {
return mInner.getCurrentInputMethodInfoAsUser(userId);
}
@Override
public List<InputMethodInfo> getInputMethodList(
- int userId, @DirectBootAwareness int directBootAwareness) throws RemoteException {
+ int userId, @DirectBootAwareness int directBootAwareness) {
return mInner.getInputMethodList(userId, directBootAwareness);
}
@Override
- public List<InputMethodInfo> getEnabledInputMethodList(int userId) throws RemoteException {
+ public List<InputMethodInfo> getEnabledInputMethodList(int userId) {
return mInner.getEnabledInputMethodList(userId);
}
@Override
public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
- boolean allowsImplicitlyEnabledSubtypes, int userId)
- throws RemoteException {
+ boolean allowsImplicitlyEnabledSubtypes, int userId) {
return mInner.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes,
userId);
}
@Override
- public InputMethodSubtype getLastInputMethodSubtype(int userId) throws RemoteException {
+ public InputMethodSubtype getLastInputMethodSubtype(int userId) {
return mInner.getLastInputMethodSubtype(userId);
}
@Override
public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
@Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
- int lastClickTooType, ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason)
- throws RemoteException {
+ @MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason) {
offload(
() -> {
if (!mInner.showSoftInput(
@@ -172,7 +173,7 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
windowToken,
statsToken,
flags,
- lastClickTooType,
+ lastClickToolType,
resultReceiver,
reason)) {
sendResultReceiverFailure(resultReceiver);
@@ -184,8 +185,7 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
@Override
public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
@Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
- ResultReceiver resultReceiver, @SoftInputShowHideReason int reason)
- throws RemoteException {
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
offload(
() -> {
if (!mInner.hideSoftInput(
@@ -196,18 +196,23 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
return true;
}
- private void sendResultReceiverFailure(ResultReceiver resultReceiver) {
- resultReceiver.send(
- mIsInputShown.getAsBoolean()
+ private void sendResultReceiverFailure(@Nullable ResultReceiver resultReceiver) {
+ if (resultReceiver == null) {
+ return;
+ }
+ final boolean isInputShown;
+ synchronized (ImfLock.class) {
+ isInputShown = mInner.isInputShownLocked();
+ }
+ resultReceiver.send(isInputShown
? InputMethodManager.RESULT_UNCHANGED_SHOWN
: InputMethodManager.RESULT_UNCHANGED_HIDDEN,
null);
}
@Override
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
- public void hideSoftInputFromServerForTest() throws RemoteException {
- super.hideSoftInputFromServerForTest_enforcePermission();
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+ public void hideSoftInputFromServerForTest() {
mInner.hideSoftInputFromServerForTest();
}
@@ -222,8 +227,7 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq)
- throws RemoteException {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq) {
offload(() -> {
InputBindResult result = mInner.startInputOrWindowGainedFocus(startInputReason, client,
windowToken, startInputFlags, softInputMode, windowFlags,
@@ -246,99 +250,92 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher)
- throws RemoteException {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
// Should never be called when flag is enabled i.e. when this proxy is used.
return null;
}
@Override
public void showInputMethodPickerFromClient(IInputMethodClient client,
- int auxiliarySubtypeMode)
- throws RemoteException {
+ int auxiliarySubtypeMode) {
offload(() -> mInner.showInputMethodPickerFromClient(client, auxiliarySubtypeMode));
}
- @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
- public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId)
- throws RemoteException {
+ public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
mInner.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
}
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
@Override
- public boolean isInputMethodPickerShownForTest() throws RemoteException {
- super.isInputMethodPickerShownForTest_enforcePermission();
+ public boolean isInputMethodPickerShownForTest() {
return mInner.isInputMethodPickerShownForTest();
}
@Override
- public InputMethodSubtype getCurrentInputMethodSubtype(int userId) throws RemoteException {
+ public InputMethodSubtype getCurrentInputMethodSubtype(int userId) {
return mInner.getCurrentInputMethodSubtype(userId);
}
@Override
public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
- @UserIdInt int userId) throws RemoteException {
+ @UserIdInt int userId) {
mInner.setAdditionalInputMethodSubtypes(imiId, subtypes, userId);
}
@Override
public void setExplicitlyEnabledInputMethodSubtypes(String imeId,
- @NonNull int[] subtypeHashCodes, @UserIdInt int userId) throws RemoteException {
+ @NonNull int[] subtypeHashCodes, @UserIdInt int userId) {
mInner.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId);
}
@Override
- public int getInputMethodWindowVisibleHeight(IInputMethodClient client)
- throws RemoteException {
+ public int getInputMethodWindowVisibleHeight(IInputMethodClient client) {
return mInner.getInputMethodWindowVisibleHeight(client);
}
@Override
- public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible)
- throws RemoteException {
+ public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
// Already async TODO(b/293640003): ordering issues?
mInner.reportPerceptibleAsync(windowToken, perceptible);
}
- @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
@Override
- public void removeImeSurface() throws RemoteException {
+ public void removeImeSurface() {
mInner.removeImeSurface();
}
@Override
- public void removeImeSurfaceFromWindowAsync(IBinder windowToken) throws RemoteException {
+ public void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
mInner.removeImeSurfaceFromWindowAsync(windowToken);
}
@Override
- public void startProtoDump(byte[] bytes, int i, String s) throws RemoteException {
+ public void startProtoDump(byte[] bytes, int i, String s) {
mInner.startProtoDump(bytes, i, s);
}
@Override
- public boolean isImeTraceEnabled() throws RemoteException {
+ public boolean isImeTraceEnabled() {
return mInner.isImeTraceEnabled();
}
- @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
@Override
- public void startImeTrace() throws RemoteException {
+ public void startImeTrace() {
mInner.startImeTrace();
}
- @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
@Override
- public void stopImeTrace() throws RemoteException {
+ public void stopImeTrace() {
mInner.stopImeTrace();
}
@Override
- public void startStylusHandwriting(IInputMethodClient client)
- throws RemoteException {
+ public void startStylusHandwriting(IInputMethodClient client) {
offload(() -> mInner.startStylusHandwriting(client));
}
@@ -346,7 +343,7 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
public void startConnectionlessStylusHandwriting(IInputMethodClient client, int userId,
@Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName,
@Nullable String delegatorPackageName,
- @NonNull IConnectionlessHandwritingCallback callback) throws RemoteException {
+ @NonNull IConnectionlessHandwritingCallback callback) {
offload(() -> mInner.startConnectionlessStylusHandwriting(
client, userId, cursorAnchorInfo, delegatePackageName, delegatorPackageName,
callback));
@@ -360,14 +357,11 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
@NonNull String delegatorPackageName,
@InputMethodManager.HandwritingDelegateFlags int flags) {
try {
- return CompletableFuture.supplyAsync(() -> {
- try {
- return mInner.acceptStylusHandwritingDelegation(
- client, userId, delegatePackageName, delegatorPackageName, flags);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }, this::offload).get();
+ return CompletableFuture.supplyAsync(() ->
+ mInner.acceptStylusHandwritingDelegation(
+ client, userId, delegatePackageName, delegatorPackageName,
+ flags),
+ this::offload).get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
@@ -381,8 +375,7 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
@UserIdInt int userId,
@NonNull String delegatePackageName,
@NonNull String delegatorPackageName,
- @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback)
- throws RemoteException {
+ @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback) {
offload(() -> mInner.acceptStylusHandwritingDelegationAsync(
client, userId, delegatePackageName, delegatorPackageName, flags, callback));
}
@@ -398,52 +391,45 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
}
@Override
- public boolean isStylusHandwritingAvailableAsUser(int userId, boolean connectionless)
- throws RemoteException {
+ public boolean isStylusHandwritingAvailableAsUser(int userId, boolean connectionless) {
return mInner.isStylusHandwritingAvailableAsUser(userId, connectionless);
}
- @EnforcePermission("android.permission.TEST_INPUT_METHOD")
+ @IInputMethodManagerImpl.PermissionVerified("android.permission.TEST_INPUT_METHOD")
@Override
- public void addVirtualStylusIdForTestSession(IInputMethodClient client)
- throws RemoteException {
+ public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
mInner.addVirtualStylusIdForTestSession(client);
}
- @EnforcePermission("android.permission.TEST_INPUT_METHOD")
+ @IInputMethodManagerImpl.PermissionVerified("android.permission.TEST_INPUT_METHOD")
@Override
- public void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout)
- throws RemoteException {
+ public void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout) {
mInner.setStylusWindowIdleTimeoutForTest(client, timeout);
}
@Override
- public IImeTracker getImeTrackerService() throws RemoteException {
+ public IImeTracker getImeTrackerService() {
return mInner.getImeTrackerService();
}
@BinderThread
@Override
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
- @Nullable FileDescriptor err,
- @NonNull String[] args, @Nullable ShellCallback callback,
- @NonNull ResultReceiver resultReceiver) throws RemoteException {
- ((InputMethodManagerService) mInner).onShellCommand(
- in, out, err, args, callback, resultReceiver);
+ @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver, @NonNull Binder self) {
+ mInner.onShellCommand(in, out, err, args, callback, resultReceiver, self);
}
@Override
- protected void dump(@NonNull FileDescriptor fd,
- @NonNull PrintWriter fout,
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {
- ((InputMethodManagerService) mInner).dump(fd, fout, args);
+ mInner.dump(fd, fout, args);
}
private void sendOnStartInputResult(
IInputMethodClient client, InputBindResult res, int startInputSeq) {
synchronized (ImfLock.class) {
- InputMethodManagerService service = (InputMethodManagerService) mInner;
- final ClientState cs = service.getClientState(client);
+ final ClientState cs = mInner.getClientStateLocked(client);
if (cs != null && cs.mClient != null) {
cs.mClient.onStartInputResult(res, startInputSeq);
} else {
diff --git a/services/core/java/com/android/server/locksettings/TEST_MAPPING b/services/core/java/com/android/server/locksettings/TEST_MAPPING
index ddf3d76e97ae..256d9ba86a6f 100644
--- a/services/core/java/com/android/server/locksettings/TEST_MAPPING
+++ b/services/core/java/com/android/server/locksettings/TEST_MAPPING
@@ -24,5 +24,11 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ // TODO(b/332974906): Promote in presubmit-large.
+ "name": "CtsDevicePolicyManagerTestCases_LockSettings_NoFlakes"
+ }
]
}
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 0cd7654f70ea..dfb2b0a750e3 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -157,6 +157,11 @@ public class MediaSession2Record extends MediaSessionRecordImpl {
}
@Override
+ public void expireTempEngaged() {
+ // NA as MediaSession2 doesn't support UserEngagementStates for FGS.
+ }
+
+ @Override
public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
KeyEvent ke, int sequenceId, ResultReceiver cb) {
// TODO(jaewan): Implement.
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index eb4e6e44c8a4..194ab04817ec 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -24,6 +24,7 @@ import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_L
import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -85,6 +86,8 @@ import com.android.server.LocalServices;
import com.android.server.uri.UriGrantsManagerInternal;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -225,6 +228,49 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
private int mPolicies;
+ private @UserEngagementState int mUserEngagementState = USER_DISENGAGED;
+
+ @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARY_ENGAGED, USER_DISENGAGED})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface UserEngagementState {}
+
+ /**
+ * Indicates that the session is active and in one of the user engaged states.
+ *
+ * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
+ */
+ private static final int USER_PERMANENTLY_ENGAGED = 0;
+
+ /**
+ * Indicates that the session is active and in {@link PlaybackState#STATE_PAUSED} state.
+ *
+ * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
+ */
+ private static final int USER_TEMPORARY_ENGAGED = 1;
+
+ /**
+ * Indicates that the session is either not active or in one of the user disengaged states
+ *
+ * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
+ */
+ private static final int USER_DISENGAGED = 2;
+
+ /**
+ * Indicates the duration of the temporary engaged states.
+ *
+ * <p>Some {@link MediaSession} states like {@link PlaybackState#STATE_PAUSED} are temporarily
+ * engaged, meaning the corresponding session is only considered in an engaged state for the
+ * duration of this timeout, and only if coming from an engaged state.
+ *
+ * <p>For example, if a session is transitioning from a user-engaged state {@link
+ * PlaybackState#STATE_PLAYING} to a temporary user-engaged state {@link
+ * PlaybackState#STATE_PAUSED}, then the session will be considered in a user-engaged state for
+ * the duration of this timeout, starting at the transition instant. However, a temporary
+ * user-engaged state is not considered user-engaged when transitioning from a non-user engaged
+ * state {@link PlaybackState#STATE_STOPPED}.
+ */
+ private static final int TEMP_USER_ENGAGED_TIMEOUT = 600000;
+
public MediaSessionRecord(
int ownerPid,
int ownerUid,
@@ -548,6 +594,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
mSessionCb.mCb.asBinder().unlinkToDeath(this, 0);
mDestroyed = true;
mPlaybackState = null;
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
mHandler.post(MessageHandler.MSG_DESTROYED);
}
}
@@ -559,6 +606,12 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
+ @Override
+ public void expireTempEngaged() {
+ mHandler.removeCallbacks(mHandleTempEngagedSessionTimeout);
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
+ }
+
/**
* Sends media button.
*
@@ -849,7 +902,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -876,7 +929,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -911,7 +964,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -938,7 +991,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -965,7 +1018,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -992,7 +1045,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -1017,7 +1070,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -1042,7 +1095,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
// After notifying clear all listeners
- mControllerCallbackHolders.clear();
+ removeControllerHoldersSafely(null);
}
private PlaybackState getStateWithUpdatedPosition() {
@@ -1090,6 +1143,17 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
return -1;
}
+ private void removeControllerHoldersSafely(
+ Collection<ISessionControllerCallbackHolder> holders) {
+ synchronized (mLock) {
+ if (holders == null) {
+ mControllerCallbackHolders.clear();
+ } else {
+ mControllerCallbackHolders.removeAll(holders);
+ }
+ }
+ }
+
private PlaybackInfo getVolumeAttributes() {
int volumeType;
AudioAttributes attributes;
@@ -1118,6 +1182,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
};
+ private final Runnable mHandleTempEngagedSessionTimeout =
+ () -> {
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
+ };
+
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
private static boolean componentNameExists(
@NonNull ComponentName componentName, @NonNull Context context, int userId) {
@@ -1134,6 +1203,40 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
return !resolveInfos.isEmpty();
}
+ private void updateUserEngagedStateIfNeededLocked(boolean isTimeoutExpired) {
+ int oldUserEngagedState = mUserEngagementState;
+ int newUserEngagedState;
+ if (!isActive() || mPlaybackState == null) {
+ newUserEngagedState = USER_DISENGAGED;
+ } else if (isActive() && mPlaybackState.isActive()) {
+ newUserEngagedState = USER_PERMANENTLY_ENGAGED;
+ } else if (mPlaybackState.getState() == PlaybackState.STATE_PAUSED) {
+ newUserEngagedState =
+ oldUserEngagedState == USER_PERMANENTLY_ENGAGED || !isTimeoutExpired
+ ? USER_TEMPORARY_ENGAGED
+ : USER_DISENGAGED;
+ } else {
+ newUserEngagedState = USER_DISENGAGED;
+ }
+ if (oldUserEngagedState == newUserEngagedState) {
+ return;
+ }
+
+ if (newUserEngagedState == USER_TEMPORARY_ENGAGED) {
+ mHandler.postDelayed(mHandleTempEngagedSessionTimeout, TEMP_USER_ENGAGED_TIMEOUT);
+ } else if (oldUserEngagedState == USER_TEMPORARY_ENGAGED) {
+ mHandler.removeCallbacks(mHandleTempEngagedSessionTimeout);
+ }
+
+ boolean wasUserEngaged = oldUserEngagedState != USER_DISENGAGED;
+ boolean isNowUserEngaged = newUserEngagedState != USER_DISENGAGED;
+ mUserEngagementState = newUserEngagedState;
+ if (wasUserEngaged != isNowUserEngaged) {
+ mService.onSessionUserEngagementStateChange(
+ /* mediaSessionRecord= */ this, /* isUserEngaged= */ isNowUserEngaged);
+ }
+ }
+
private final class SessionStub extends ISession.Stub {
@Override
public void destroySession() throws RemoteException {
@@ -1171,8 +1274,10 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
.logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
callingUid, callingPid);
}
-
- mIsActive = active;
+ synchronized (mLock) {
+ mIsActive = active;
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
+ }
long token = Binder.clearCallingIdentity();
try {
mService.onSessionActiveStateChanged(MediaSessionRecord.this, mPlaybackState);
@@ -1330,6 +1435,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
&& TRANSITION_PRIORITY_STATES.contains(newState));
synchronized (mLock) {
mPlaybackState = state;
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
}
final long token = Binder.clearCallingIdentity();
try {
@@ -1351,12 +1457,18 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
@Override
public IBinder getBinderForSetQueue() throws RemoteException {
- return new ParcelableListBinder<QueueItem>((list) -> {
- synchronized (mLock) {
- mQueue = list;
- }
- mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
- });
+ return new ParcelableListBinder<QueueItem>(
+ (list) -> {
+ // Checking list items are instanceof QueueItem to validate against
+ // malicious apps calling it directly via reflection with non compilable
+ // items. See b/317048338 for more details
+ List<QueueItem> sanitizedQueue =
+ list.stream().filter(it -> it instanceof QueueItem).toList();
+ synchronized (mLock) {
+ mQueue = sanitizedQueue;
+ }
+ mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
+ });
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 09991995099e..b57b14835987 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -196,6 +196,12 @@ public abstract class MediaSessionRecordImpl {
*/
public abstract boolean isClosed();
+ /**
+ * Note: This method is only used for testing purposes If the session is temporary engaged, the
+ * timeout will expire and it will become disengaged.
+ */
+ public abstract void expireTempEngaged();
+
@Override
public final boolean equals(Object o) {
if (this == o) return true;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 53c32cf31d21..74adf5e0d52c 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -367,11 +367,13 @@ public class MediaSessionService extends SystemService implements Monitor {
}
boolean isUserEngaged = isUserEngaged(record, playbackState);
- Log.d(TAG, "onSessionActiveStateChanged: "
- + "record=" + record
- + "playbackState=" + playbackState
- + "allowRunningInForeground=" + isUserEngaged);
- setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged);
+ Log.d(
+ TAG,
+ "onSessionActiveStateChanged:"
+ + " record="
+ + record
+ + " playbackState="
+ + playbackState);
reportMediaInteractionEvent(record, isUserEngaged);
mHandler.postSessionsChanged(record);
}
@@ -479,11 +481,13 @@ public class MediaSessionService extends SystemService implements Monitor {
}
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
boolean isUserEngaged = isUserEngaged(record, playbackState);
- Log.d(TAG, "onSessionPlaybackStateChanged: "
- + "record=" + record
- + "playbackState=" + playbackState
- + "allowRunningInForeground=" + isUserEngaged);
- setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged);
+ Log.d(
+ TAG,
+ "onSessionPlaybackStateChanged:"
+ + " record="
+ + record
+ + " playbackState="
+ + playbackState);
reportMediaInteractionEvent(record, isUserEngaged);
}
}
@@ -650,68 +654,112 @@ public class MediaSessionService extends SystemService implements Monitor {
session.close();
Log.d(TAG, "destroySessionLocked: record=" + session);
- setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);
+
reportMediaInteractionEvent(session, /* userEngaged= */ false);
mHandler.postSessionsChanged(session);
}
- private void setForegroundServiceAllowance(
- MediaSessionRecordImpl record, boolean allowRunningInForeground) {
- if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
- return;
- }
- ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
- record.getForegroundServiceDelegationOptions();
- if (foregroundServiceDelegationOptions == null) {
- return;
- }
- if (allowRunningInForeground) {
- onUserSessionEngaged(record);
+ void onSessionUserEngagementStateChange(
+ MediaSessionRecordImpl mediaSessionRecord, boolean isUserEngaged) {
+ if (isUserEngaged) {
+ addUserEngagedSession(mediaSessionRecord);
+ startFgsIfSessionIsLinkedToNotification(mediaSessionRecord);
} else {
- onUserDisengaged(record);
+ removeUserEngagedSession(mediaSessionRecord);
+ stopFgsIfNoSessionIsLinkedToNotification(mediaSessionRecord);
}
}
- private void onUserSessionEngaged(MediaSessionRecordImpl mediaSessionRecord) {
+ private void addUserEngagedSession(MediaSessionRecordImpl mediaSessionRecord) {
synchronized (mLock) {
int uid = mediaSessionRecord.getUid();
mUserEngagedSessionsForFgs.putIfAbsent(uid, new HashSet<>());
mUserEngagedSessionsForFgs.get(uid).add(mediaSessionRecord);
+ }
+ }
+
+ private void removeUserEngagedSession(MediaSessionRecordImpl mediaSessionRecord) {
+ synchronized (mLock) {
+ int uid = mediaSessionRecord.getUid();
+ Set<MediaSessionRecordImpl> mUidUserEngagedSessionsForFgs =
+ mUserEngagedSessionsForFgs.get(uid);
+ if (mUidUserEngagedSessionsForFgs == null) {
+ return;
+ }
+
+ mUidUserEngagedSessionsForFgs.remove(mediaSessionRecord);
+ if (mUidUserEngagedSessionsForFgs.isEmpty()) {
+ mUserEngagedSessionsForFgs.remove(uid);
+ }
+ }
+ }
+
+ private void startFgsIfSessionIsLinkedToNotification(
+ MediaSessionRecordImpl mediaSessionRecord) {
+ Log.d(TAG, "startFgsIfSessionIsLinkedToNotification: record=" + mediaSessionRecord);
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ synchronized (mLock) {
+ int uid = mediaSessionRecord.getUid();
for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid, Set.of())) {
if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) {
- mActivityManagerInternal.startForegroundServiceDelegate(
- mediaSessionRecord.getForegroundServiceDelegationOptions(),
- /* connection= */ null);
+ startFgsDelegate(mediaSessionRecord.getForegroundServiceDelegationOptions());
return;
}
}
}
}
- private void onUserDisengaged(MediaSessionRecordImpl mediaSessionRecord) {
+ private void startFgsDelegate(
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mActivityManagerInternal.startForegroundServiceDelegate(
+ foregroundServiceDelegationOptions, /* connection= */ null);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void stopFgsIfNoSessionIsLinkedToNotification(
+ MediaSessionRecordImpl mediaSessionRecord) {
+ Log.d(TAG, "stopFgsIfNoSessionIsLinkedToNotification: record=" + mediaSessionRecord);
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
synchronized (mLock) {
int uid = mediaSessionRecord.getUid();
- if (mUserEngagedSessionsForFgs.containsKey(uid)) {
- mUserEngagedSessionsForFgs.get(uid).remove(mediaSessionRecord);
- if (mUserEngagedSessionsForFgs.get(uid).isEmpty()) {
- mUserEngagedSessionsForFgs.remove(uid);
- }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ mediaSessionRecord.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null) {
+ return;
}
- boolean shouldStopFgs = true;
- for (MediaSessionRecordImpl sessionRecord :
+ for (MediaSessionRecordImpl record :
mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
- for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid,
- Set.of())) {
- if (sessionRecord.isLinkedToNotification(mediaNotification)) {
- shouldStopFgs = false;
+ for (Notification mediaNotification :
+ mMediaNotifications.getOrDefault(uid, Set.of())) {
+ if (record.isLinkedToNotification(mediaNotification)) {
+ // A user engaged session linked with a media notification is found.
+ // We shouldn't call stop FGS in this case.
+ return;
}
}
}
- if (shouldStopFgs) {
- mActivityManagerInternal.stopForegroundServiceDelegate(
- mediaSessionRecord.getForegroundServiceDelegationOptions());
- }
+
+ stopFgsDelegate(foregroundServiceDelegationOptions);
+ }
+ }
+
+ private void stopFgsDelegate(
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mActivityManagerInternal.stopForegroundServiceDelegate(
+ foregroundServiceDelegationOptions);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
@@ -2502,7 +2550,6 @@ public class MediaSessionService extends SystemService implements Monitor {
}
MediaSessionRecord session = null;
MediaButtonReceiverHolder mediaButtonReceiverHolder = null;
-
if (mCustomMediaKeyDispatcher != null) {
MediaSession.Token token = mCustomMediaKeyDispatcher.getMediaSession(
keyEvent, uid, asSystemService);
@@ -2630,6 +2677,18 @@ public class MediaSessionService extends SystemService implements Monitor {
&& streamType <= AudioManager.STREAM_NOTIFICATION;
}
+ @Override
+ public void expireTempEngagedSessions() {
+ synchronized (mLock) {
+ for (Set<MediaSessionRecordImpl> uidSessions :
+ mUserEngagedSessionsForFgs.values()) {
+ for (MediaSessionRecordImpl sessionRecord : uidSessions) {
+ sessionRecord.expireTempEngaged();
+ }
+ }
+ }
+ }
+
private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
private final String mPackageName;
private final int mPid;
@@ -3127,7 +3186,6 @@ public class MediaSessionService extends SystemService implements Monitor {
super.onNotificationPosted(sbn);
Notification postedNotification = sbn.getNotification();
int uid = sbn.getUid();
-
if (!postedNotification.isMediaNotification()) {
return;
}
@@ -3138,11 +3196,9 @@ public class MediaSessionService extends SystemService implements Monitor {
mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
mediaSessionRecord.getForegroundServiceDelegationOptions();
- if (mediaSessionRecord.isLinkedToNotification(postedNotification)
- && foregroundServiceDelegationOptions != null) {
- mActivityManagerInternal.startForegroundServiceDelegate(
- foregroundServiceDelegationOptions,
- /* connection= */ null);
+ if (foregroundServiceDelegationOptions != null
+ && mediaSessionRecord.isLinkedToNotification(postedNotification)) {
+ startFgsDelegate(foregroundServiceDelegationOptions);
return;
}
}
@@ -3173,21 +3229,7 @@ public class MediaSessionService extends SystemService implements Monitor {
return;
}
- boolean shouldStopFgs = true;
- for (MediaSessionRecordImpl mediaSessionRecord :
- mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
- for (Notification mediaNotification :
- mMediaNotifications.getOrDefault(uid, Set.of())) {
- if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) {
- shouldStopFgs = false;
- }
- }
- }
- if (shouldStopFgs
- && notificationRecord.getForegroundServiceDelegationOptions() != null) {
- mActivityManagerInternal.stopForegroundServiceDelegate(
- notificationRecord.getForegroundServiceDelegationOptions());
- }
+ stopFgsIfNoSessionIsLinkedToNotification(notificationRecord);
}
}
diff --git a/services/core/java/com/android/server/media/MediaShellCommand.java b/services/core/java/com/android/server/media/MediaShellCommand.java
index a56380827f2c..a20de3198d2c 100644
--- a/services/core/java/com/android/server/media/MediaShellCommand.java
+++ b/services/core/java/com/android/server/media/MediaShellCommand.java
@@ -92,6 +92,8 @@ public class MediaShellCommand extends ShellCommand {
runMonitor();
} else if (cmd.equals("volume")) {
runVolume();
+ } else if (cmd.equals("expire-temp-engaged-sessions")) {
+ expireTempEngagedSessions();
} else {
showError("Error: unknown command '" + cmd + "'");
return -1;
@@ -367,4 +369,8 @@ public class MediaShellCommand extends ShellCommand {
private void runVolume() throws Exception {
VolumeCtrl.run(this);
}
+
+ private void expireTempEngagedSessions() throws Exception {
+ mSessionService.expireTempEngagedSessions();
+ }
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index e1e2b3efd1eb..3949dfe01761 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1617,7 +1617,8 @@ abstract public class ManagedServices {
intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);
final ActivityOptions activityOptions = ActivityOptions.makeBasic();
- activityOptions.setIgnorePendingIntentCreatorForegroundState(true);
+ activityOptions.setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
final PendingIntent pendingIntent = PendingIntent.getActivity(
mContext, 0, new Intent(mConfig.settingsAction), PendingIntent.FLAG_IMMUTABLE,
activityOptions.toBundle());
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 956e10c79246..4dbb9e884edb 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -22,6 +22,7 @@ import static android.Manifest.permission.STATUS_BAR_SERVICE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
import static android.app.Flags.lifetimeExtensionRefactor;
import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
@@ -3399,21 +3400,21 @@ public class NotificationManagerService extends SystemService {
// ============================================================================
@Override
- public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration,
+ public boolean enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration,
boolean isUiContext, int displayId,
@Nullable ITransientNotificationCallback textCallback) {
- enqueueToast(pkg, token, text, /* callback= */ null, duration, isUiContext, displayId,
- textCallback);
+ return enqueueToast(pkg, token, text, /* callback= */ null, duration, isUiContext,
+ displayId, textCallback);
}
@Override
- public void enqueueToast(String pkg, IBinder token, ITransientNotification callback,
+ public boolean enqueueToast(String pkg, IBinder token, ITransientNotification callback,
int duration, boolean isUiContext, int displayId) {
- enqueueToast(pkg, token, /* text= */ null, callback, duration, isUiContext, displayId,
- /* textCallback= */ null);
+ return enqueueToast(pkg, token, /* text= */ null, callback, duration, isUiContext,
+ displayId, /* textCallback= */ null);
}
- private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text,
+ private boolean enqueueToast(String pkg, IBinder token, @Nullable CharSequence text,
@Nullable ITransientNotification callback, int duration, boolean isUiContext,
int displayId, @Nullable ITransientNotificationCallback textCallback) {
if (DBG) {
@@ -3425,7 +3426,7 @@ public class NotificationManagerService extends SystemService {
|| (text != null && callback != null) || token == null) {
Slog.e(TAG, "Not enqueuing toast. pkg=" + pkg + " text=" + text + " callback="
+ " token=" + token);
- return;
+ return false;
}
final int callingUid = Binder.getCallingUid();
@@ -3451,7 +3452,7 @@ public class NotificationManagerService extends SystemService {
boolean isAppRenderedToast = (callback != null);
if (!checkCanEnqueueToast(pkg, callingUid, displayId, isAppRenderedToast,
isSystemToast)) {
- return;
+ return false;
}
synchronized (mToastQueue) {
@@ -3477,7 +3478,7 @@ public class NotificationManagerService extends SystemService {
if (count >= MAX_PACKAGE_TOASTS) {
Slog.e(TAG, "Package has already queued " + count
+ " toasts. Not showing more. Package=" + pkg);
- return;
+ return false;
}
}
}
@@ -3513,6 +3514,7 @@ public class NotificationManagerService extends SystemService {
Binder.restoreCallingIdentity(callingId);
}
}
+ return true;
}
@GuardedBy("mToastQueue")
@@ -3597,8 +3599,8 @@ public class NotificationManagerService extends SystemService {
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING)
@Override
+ @EnforcePermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING)
public void setToastRateLimitingEnabled(boolean enable) {
super.setToastRateLimitingEnabled_enforcePermission();
@@ -4523,7 +4525,6 @@ public class NotificationManagerService extends SystemService {
return getActiveNotificationsWithAttribution(callingPkg, null);
}
- @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
/**
* System-only API for getting a list of current (i.e. not cleared) notifications.
*
@@ -4531,6 +4532,7 @@ public class NotificationManagerService extends SystemService {
* @returns A list of all the notifications, in natural order.
*/
@Override
+ @EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
public StatusBarNotification[] getActiveNotificationsWithAttribution(String callingPkg,
String callingAttributionTag) {
// enforce() will ensure the calling uid has the correct permission
@@ -4548,9 +4550,9 @@ public class NotificationManagerService extends SystemService {
});
// noteOp will check to make sure the callingPkg matches the uid
- if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
- callingAttributionTag, null)
- == MODE_ALLOWED) {
+ int mode = mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
+ callingAttributionTag, null);
+ if (mode == MODE_ALLOWED || mode == MODE_DEFAULT) {
synchronized (mNotificationLock) {
final int N = mNotificationList.size();
for (int i = 0; i < N; i++) {
@@ -4650,12 +4652,12 @@ public class NotificationManagerService extends SystemService {
includeSnoozed);
}
- @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
/**
* System-only API for getting a list of recent (cleared, no longer shown) notifications.
*/
@Override
@RequiresPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
+ @EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
public StatusBarNotification[] getHistoricalNotificationsWithAttribution(String callingPkg,
String callingAttributionTag, int count, boolean includeSnoozed) {
// enforce() will ensure the calling uid has the correct permission
@@ -4665,9 +4667,9 @@ public class NotificationManagerService extends SystemService {
int uid = Binder.getCallingUid();
// noteOp will check to make sure the callingPkg matches the uid
- if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
- callingAttributionTag, null)
- == MODE_ALLOWED) {
+ int mode = mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
+ callingAttributionTag, null);
+ if (mode == MODE_ALLOWED || mode == MODE_DEFAULT) {
synchronized (mArchive) {
tmp = mArchive.getArray(mUm, count, includeSnoozed);
}
@@ -4675,7 +4677,6 @@ public class NotificationManagerService extends SystemService {
return tmp;
}
- @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
/**
* System-only API for getting a list of historical notifications. May contain multiple days
* of notifications.
@@ -4683,6 +4684,7 @@ public class NotificationManagerService extends SystemService {
@Override
@WorkerThread
@RequiresPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
+ @EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
public NotificationHistory getNotificationHistory(String callingPkg,
String callingAttributionTag) {
// enforce() will ensure the calling uid has the correct permission
@@ -4690,9 +4692,9 @@ public class NotificationManagerService extends SystemService {
int uid = Binder.getCallingUid();
// noteOp will check to make sure the callingPkg matches the uid
- if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
- callingAttributionTag, null)
- == MODE_ALLOWED) {
+ int mode = mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
+ callingAttributionTag, null);
+ if (mode == MODE_ALLOWED || mode == MODE_DEFAULT) {
IntArray currentUserIds = mUserProfiles.getCurrentProfileIds();
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "notifHistoryReadHistory");
try {
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 7b12d8686f0e..68e0eaaf31cd 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -27,6 +27,10 @@ import android.util.ArrayMap;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -44,7 +48,13 @@ public class RankingHelper {
private final Context mContext;
private final RankingHandler mRankingHandler;
-
+ @UsesReflection(
+ value = {
+ @KeepTarget(
+ kind = KeepItemKind.CLASS_AND_MEMBERS,
+ instanceOfClassConstantExclusive = NotificationSignalExtractor.class,
+ methodName = "<init>")
+ })
public RankingHelper(Context context, RankingHandler rankingHandler, RankingConfig config,
ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) {
mContext = context;
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 4b8e4852aee7..6c93fe787816 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -32,6 +32,7 @@ import static android.os.Process.INVALID_UID;
import static android.os.Trace.TRACE_TAG_RRO;
import static android.os.Trace.traceBegin;
import static android.os.Trace.traceEnd;
+
import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException;
import android.annotation.NonNull;
@@ -279,7 +280,8 @@ public final class OverlayManagerService extends SystemService {
HandlerThread packageMonitorThread = new HandlerThread(TAG);
packageMonitorThread.start();
- mPackageMonitor.register(context, packageMonitorThread.getLooper(), true);
+ mPackageMonitor.register(
+ context, packageMonitorThread.getLooper(), UserHandle.ALL, true);
final IntentFilter userFilter = new IntentFilter();
userFilter.addAction(ACTION_USER_ADDED);
@@ -369,17 +371,17 @@ public final class OverlayManagerService extends SystemService {
@Override
public void onPackageAppearedWithExtras(String packageName, Bundle extras) {
- handlePackageAdd(packageName, extras);
+ handlePackageAdd(packageName, extras, getChangingUserId());
}
@Override
public void onPackageChangedWithExtras(String packageName, Bundle extras) {
- handlePackageChange(packageName, extras);
+ handlePackageChange(packageName, extras, getChangingUserId());
}
@Override
public void onPackageDisappearedWithExtras(String packageName, Bundle extras) {
- handlePackageRemove(packageName, extras);
+ handlePackageRemove(packageName, extras, getChangingUserId());
}
}
@@ -393,54 +395,45 @@ public final class OverlayManagerService extends SystemService {
return userIds;
}
- private void handlePackageAdd(String packageName, Bundle extras) {
+ private void handlePackageAdd(String packageName, Bundle extras, int userId) {
final boolean replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
- final int uid = extras.getInt(Intent.EXTRA_UID, 0);
- final int[] userIds = getUserIds(uid);
if (replacing) {
- onPackageReplaced(packageName, userIds);
+ onPackageReplaced(packageName, userId);
} else {
- onPackageAdded(packageName, userIds);
+ onPackageAdded(packageName, userId);
}
}
- private void handlePackageChange(String packageName, Bundle extras) {
- final int uid = extras.getInt(Intent.EXTRA_UID, 0);
- final int[] userIds = getUserIds(uid);
+ private void handlePackageChange(String packageName, Bundle extras, int userId) {
if (!ACTION_OVERLAY_CHANGED.equals(extras.getString(EXTRA_REASON))) {
- onPackageChanged(packageName, userIds);
+ onPackageChanged(packageName, userId);
}
}
- private void handlePackageRemove(String packageName, Bundle extras) {
+ private void handlePackageRemove(String packageName, Bundle extras, int userId) {
final boolean replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
final boolean systemUpdateUninstall =
extras.getBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, false);
- final int uid = extras.getInt(Intent.EXTRA_UID, 0);
- final int[] userIds = getUserIds(uid);
if (replacing) {
- onPackageReplacing(packageName, systemUpdateUninstall, userIds);
+ onPackageReplacing(packageName, systemUpdateUninstall, userId);
} else {
- onPackageRemoved(packageName, userIds);
+ onPackageRemoved(packageName, userId);
}
}
- private void onPackageAdded(@NonNull final String packageName,
- @NonNull final int[] userIds) {
+ private void onPackageAdded(@NonNull final String packageName, final int userId) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageAdded " + packageName);
- for (final int userId : userIds) {
- synchronized (mLock) {
- var packageState = mPackageManager.onPackageAdded(packageName, userId);
- if (packageState != null && !mPackageManager.isInstantApp(packageName,
- userId)) {
- try {
- updateTargetPackagesLocked(
- mImpl.onPackageAdded(packageName, userId));
- } catch (OperationFailedException e) {
- Slog.e(TAG, "onPackageAdded internal error", e);
- }
+ synchronized (mLock) {
+ var packageState = mPackageManager.onPackageAdded(packageName, userId);
+ if (packageState != null && !mPackageManager.isInstantApp(packageName,
+ userId)) {
+ try {
+ updateTargetPackagesLocked(
+ mImpl.onPackageAdded(packageName, userId));
+ } catch (OperationFailedException e) {
+ Slog.e(TAG, "onPackageAdded internal error", e);
}
}
}
@@ -449,21 +442,18 @@ public final class OverlayManagerService extends SystemService {
}
}
- private void onPackageChanged(@NonNull final String packageName,
- @NonNull final int[] userIds) {
+ private void onPackageChanged(@NonNull final String packageName, final int userId) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageChanged " + packageName);
- for (int userId : userIds) {
- synchronized (mLock) {
- var packageState = mPackageManager.onPackageUpdated(packageName, userId);
- if (packageState != null && !mPackageManager.isInstantApp(packageName,
- userId)) {
- try {
- updateTargetPackagesLocked(
- mImpl.onPackageChanged(packageName, userId));
- } catch (OperationFailedException e) {
- Slog.e(TAG, "onPackageChanged internal error", e);
- }
+ synchronized (mLock) {
+ var packageState = mPackageManager.onPackageUpdated(packageName, userId);
+ if (packageState != null && !mPackageManager.isInstantApp(packageName,
+ userId)) {
+ try {
+ updateTargetPackagesLocked(
+ mImpl.onPackageChanged(packageName, userId));
+ } catch (OperationFailedException e) {
+ Slog.e(TAG, "onPackageChanged internal error", e);
}
}
}
@@ -473,20 +463,18 @@ public final class OverlayManagerService extends SystemService {
}
private void onPackageReplacing(@NonNull final String packageName,
- boolean systemUpdateUninstall, @NonNull final int[] userIds) {
+ boolean systemUpdateUninstall, final int userId) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName);
- for (int userId : userIds) {
- synchronized (mLock) {
- var packageState = mPackageManager.onPackageUpdated(packageName, userId);
- if (packageState != null && !mPackageManager.isInstantApp(packageName,
- userId)) {
- try {
- updateTargetPackagesLocked(mImpl.onPackageReplacing(packageName,
- systemUpdateUninstall, userId));
- } catch (OperationFailedException e) {
- Slog.e(TAG, "onPackageReplacing internal error", e);
- }
+ synchronized (mLock) {
+ var packageState = mPackageManager.onPackageUpdated(packageName, userId);
+ if (packageState != null && !mPackageManager.isInstantApp(packageName,
+ userId)) {
+ try {
+ updateTargetPackagesLocked(mImpl.onPackageReplacing(packageName,
+ systemUpdateUninstall, userId));
+ } catch (OperationFailedException e) {
+ Slog.e(TAG, "onPackageReplacing internal error", e);
}
}
}
@@ -495,21 +483,18 @@ public final class OverlayManagerService extends SystemService {
}
}
- private void onPackageReplaced(@NonNull final String packageName,
- @NonNull final int[] userIds) {
+ private void onPackageReplaced(@NonNull final String packageName, final int userId) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplaced " + packageName);
- for (int userId : userIds) {
- synchronized (mLock) {
- var packageState = mPackageManager.onPackageUpdated(packageName, userId);
- if (packageState != null && !mPackageManager.isInstantApp(packageName,
- userId)) {
- try {
- updateTargetPackagesLocked(
- mImpl.onPackageReplaced(packageName, userId));
- } catch (OperationFailedException e) {
- Slog.e(TAG, "onPackageReplaced internal error", e);
- }
+ synchronized (mLock) {
+ var packageState = mPackageManager.onPackageUpdated(packageName, userId);
+ if (packageState != null && !mPackageManager.isInstantApp(packageName,
+ userId)) {
+ try {
+ updateTargetPackagesLocked(
+ mImpl.onPackageReplaced(packageName, userId));
+ } catch (OperationFailedException e) {
+ Slog.e(TAG, "onPackageReplaced internal error", e);
}
}
}
@@ -518,15 +503,12 @@ public final class OverlayManagerService extends SystemService {
}
}
- private void onPackageRemoved(@NonNull final String packageName,
- @NonNull final int[] userIds) {
+ private void onPackageRemoved(@NonNull final String packageName, final int userId) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageRemoved " + packageName);
- for (int userId : userIds) {
- synchronized (mLock) {
- mPackageManager.onPackageRemoved(packageName, userId);
- updateTargetPackagesLocked(mImpl.onPackageRemoved(packageName, userId));
- }
+ synchronized (mLock) {
+ mPackageManager.onPackageRemoved(packageName, userId);
+ updateTargetPackagesLocked(mImpl.onPackageRemoved(packageName, userId));
}
} finally {
traceEnd(TRACE_TAG_RRO);
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 4eb8b2b980cb..c8fd7e47d80a 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -184,6 +184,16 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
throwInvalidBugreportFileForCallerException(
bugreportFile, callingInfo.second);
}
+
+ boolean keepBugreportOnRetrieval = false;
+ if (onboardingBugreportV2Enabled()) {
+ keepBugreportOnRetrieval = mBugreportFilesToPersist.contains(
+ bugreportFile);
+ }
+
+ if (!keepBugreportOnRetrieval) {
+ bugreportFilesForUid.remove(bugreportFile);
+ }
} else {
ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo);
if (bugreportFilesForCaller != null
diff --git a/services/core/java/com/android/server/os/TEST_MAPPING b/services/core/java/com/android/server/os/TEST_MAPPING
index d937af1e3fdb..50c8964b2aa4 100644
--- a/services/core/java/com/android/server/os/TEST_MAPPING
+++ b/services/core/java/com/android/server/os/TEST_MAPPING
@@ -45,6 +45,10 @@
},
{
"file_patterns": ["Bugreport[^/]*\\.java"],
+ "name": "CtsRootBugreportTestCases"
+ },
+ {
+ "file_patterns": ["Bugreport[^/]*\\.java"],
"name": "ShellTests"
}
]
diff --git a/services/core/java/com/android/server/pm/NoFilteringResolver.java b/services/core/java/com/android/server/pm/NoFilteringResolver.java
index b87256dbbd9a..712413c49f18 100644
--- a/services/core/java/com/android/server/pm/NoFilteringResolver.java
+++ b/services/core/java/com/android/server/pm/NoFilteringResolver.java
@@ -60,9 +60,12 @@ public class NoFilteringResolver extends CrossProfileResolver {
public static boolean isIntentRedirectionAllowed(Context context,
AppCloningDeviceConfigHelper appCloningDeviceConfigHelper, boolean resolveForStart,
long flags) {
+ boolean canMatchCloneProfile = (flags & PackageManager.MATCH_CLONE_PROFILE) != 0
+ || (flags & PackageManager.MATCH_CLONE_PROFILE_LONG) != 0;
return isAppCloningBuildingBlocksEnabled(context, appCloningDeviceConfigHelper)
- && (resolveForStart || (((flags & PackageManager.MATCH_CLONE_PROFILE) != 0)
- && hasPermission(context, Manifest.permission.QUERY_CLONED_APPS)));
+ && (resolveForStart
+ || (canMatchCloneProfile
+ && hasPermission(context, Manifest.permission.QUERY_CLONED_APPS)));
}
public NoFilteringResolver(ComponentResolverApi componentResolver,
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index e2f4d18bbd6d..fda4dc6087f5 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -187,7 +187,7 @@ public class PackageArchiver {
}
public static boolean isArchivingEnabled() {
- return Flags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false);
+ return Flags.archiving();
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 29320aeefde9..a5cd821e319d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -838,7 +838,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
if ((params.installFlags & PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK) != 0
&& !PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)
- && !Build.IS_DEBUGGABLE) {
+ && !Build.IS_DEBUGGABLE
+ && !PackageManagerServiceUtils.isAdoptedShell(callingUid, mContext)) {
// If the bypass flag is set, but not running as system root or shell then remove
// the flag
params.installFlags &= ~PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3eeeae7dc260..80a5f3a4c579 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1085,11 +1085,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final boolean isUpdateOwnershipEnforcementEnabled =
mPm.isUpdateOwnershipEnforcementAvailable()
&& existingUpdateOwnerPackageName != null;
+ // For an installation that un-archives an app, if the installer doesn't have the
+ // INSTALL_PACKAGES permission, the user should have already been prompted to confirm the
+ // un-archive request. There's no need for another confirmation during the installation.
+ final boolean isInstallUnarchive =
+ (params.installFlags & PackageManager.INSTALL_UNARCHIVE) != 0;
// Device owners and affiliated profile owners are allowed to silently install packages, so
// the permission check is waived if the installer is the device owner.
final boolean noUserActionNecessary = isInstallerRoot || isInstallerSystem
- || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall;
+ || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall
+ || isInstallUnarchive;
if (noUserActionNecessary) {
return userActionNotTypicallyNeededResponse;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 68cd3e463905..614828add52b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5753,17 +5753,22 @@ public class PackageManagerService implements PackageSender, TestUtilityService
@Override
public void setApplicationCategoryHint(String packageName, int categoryHint,
String callerPackageName) {
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getCallingUserId();
final FunctionalUtils.ThrowingBiFunction<PackageStateMutator.InitialState, Computer,
PackageStateMutator.Result> implementation = (initialState, computer) -> {
- if (computer.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+ if (computer.getInstantAppPackageName(callingUid) != null) {
throw new SecurityException(
"Instant applications don't have access to this method");
}
- mInjector.getSystemService(AppOpsManager.class)
- .checkPackage(Binder.getCallingUid(), callerPackageName);
+ final int callerPackageUid = computer.getPackageUid(callerPackageName, 0, userId);
+ if (callerPackageUid != callingUid) {
+ throw new SecurityException(
+ "Package " + callerPackageName + " does not belong to " + callingUid);
+ }
PackageStateInternal packageState = computer.getPackageStateForInstalledAndFiltered(
- packageName, Binder.getCallingUid(), UserHandle.getCallingUserId());
+ packageName, callingUid, userId);
if (packageState == null) {
throw new IllegalArgumentException("Unknown target package " + packageName);
}
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
index 1fe49c7d5834..7ef7ce7afb65 100644
--- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -16,6 +16,8 @@
package com.android.server.pm;
+import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_APEX;
+
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
@@ -399,7 +401,7 @@ final class PackageSessionVerifier {
final ParsedPackage parsedPackage;
try (PackageParser2 packageParser = mPackageParserSupplier.get()) {
File apexFile = new File(apexInfo.modulePath);
- parsedPackage = packageParser.parsePackage(apexFile, 0, false);
+ parsedPackage = packageParser.parsePackage(apexFile, PARSE_APEX, false);
} catch (PackageParserException e) {
throw new PackageManagerException(
PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ff41245e1e13..e3261bd1322a 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1882,11 +1882,10 @@ public class UserManagerService extends IUserManager.Stub {
&& android.multiuser.Flags.enablePrivateSpaceFeatures()) {
// Allow delayed locking since some profile types want to be able to unlock again via
// biometrics.
- ActivityManager.getService()
- .stopUserWithDelayedLocking(userId, /* force= */ true, null);
+ ActivityManager.getService().stopUserWithDelayedLocking(userId, null);
return;
}
- ActivityManager.getService().stopUser(userId, /* force= */ true, null);
+ ActivityManager.getService().stopUserWithCallback(userId, null);
}
private void logQuietModeEnabled(@UserIdInt int userId, boolean enableQuietMode,
@@ -6132,7 +6131,7 @@ public class UserManagerService extends IUserManager.Stub {
if (DBG) Slog.i(LOG_TAG, "Stopping user " + userId);
int res;
try {
- res = ActivityManager.getService().stopUser(userId, /* force= */ true,
+ res = ActivityManager.getService().stopUserWithCallback(userId,
new IStopUserCallback.Stub() {
@Override
public void userStopped(int userIdParam) {
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 324637caa82f..4e02470e087d 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -721,7 +721,8 @@ public class UserRestrictionsUtils {
if (!am.isProfileForeground(UserHandle.of(userId))
&& userId != UserHandle.USER_SYSTEM) {
try {
- ActivityManager.getService().stopUser(userId, false, null);
+ ActivityManager.getService().stopUserExceptCertainProfiles(
+ userId, false, null);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 33b5a707d9df..1e7fdfec783f 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -306,7 +306,7 @@ public final class UserTypeFactory {
R.color.black)
.setDarkThemeBadgeColors(
R.color.white)
- .setDefaultRestrictions(getDefaultProfileRestrictions())
+ .setDefaultRestrictions(getDefaultPrivateProfileRestrictions())
.setDefaultCrossProfileIntentFilters(getDefaultPrivateCrossProfileIntentFilter())
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
@@ -430,6 +430,13 @@ public final class UserTypeFactory {
return restrictions;
}
+ @VisibleForTesting
+ static Bundle getDefaultPrivateProfileRestrictions() {
+ final Bundle restrictions = getDefaultProfileRestrictions();
+ restrictions.putBoolean(UserManager.DISALLOW_BLUETOOTH_SHARING, true);
+ return restrictions;
+ }
+
private static Bundle getDefaultManagedProfileSecureSettings() {
// Only add String values to the bundle, settings are written as Strings eventually
final Bundle settings = new Bundle();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7db83d7dcc6f..b4919a4fb9ff 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -174,7 +174,6 @@ import android.service.dreams.IDreamManager;
import android.service.vr.IPersistentVrStateCallbacks;
import android.speech.RecognizerIntent;
import android.telecom.TelecomManager;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.MathUtils;
import android.util.MutableBoolean;
@@ -4121,14 +4120,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private void handleSwitchKeyboardLayout(@NonNull KeyEvent event, int direction,
IBinder focusedToken) {
- if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
- IBinder targetWindowToken =
- mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken);
- InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction,
- event.getDisplayId(), targetWindowToken);
- } else {
- mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
- }
+ IBinder targetWindowToken =
+ mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken);
+ InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction,
+ event.getDisplayId(), targetWindowToken);
}
private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 2623025cacf1..9ca4e273ac39 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -251,12 +251,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
*/
public int getCameraLensCoverState();
- /**
- * Switch the keyboard layout for the given device.
- * Direction should be +1 or -1 to go to the next or previous keyboard layout.
- */
- public void switchKeyboardLayout(int deviceId, int direction);
-
public void shutdown(boolean confirm);
public void reboot(boolean confirm);
public void rebootSafeMode(boolean confirm);
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
index 894226cf32c9..e1b4b88ed1df 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
@@ -34,11 +34,14 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.StringWriter;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.TimeZone;
/**
* This class represents aggregated power stats for a variety of power components (CPU, WiFi,
@@ -66,7 +69,7 @@ class AggregatedPowerStats {
aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs();
mPowerComponentStats = new PowerComponentAggregatedPowerStats[configs.size()];
for (int i = 0; i < configs.size(); i++) {
- mPowerComponentStats[i] = new PowerComponentAggregatedPowerStats(configs.get(i));
+ mPowerComponentStats[i] = new PowerComponentAggregatedPowerStats(this, configs.get(i));
}
}
@@ -223,7 +226,7 @@ class AggregatedPowerStats {
if (i == 0) {
baseTime = clockUpdate.monotonicTime;
sb.append("Start time: ")
- .append(DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime))
+ .append(formatDateTime(clockUpdate.currentTime))
.append(" (")
.append(baseTime)
.append(") duration: ")
@@ -235,8 +238,7 @@ class AggregatedPowerStats {
TimeUtils.formatDuration(
clockUpdate.monotonicTime - baseTime, sb,
TimeUtils.HUNDRED_DAY_FIELD_LEN + 3);
- sb.append(" ").append(
- DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime));
+ sb.append(" ").append(formatDateTime(clockUpdate.currentTime));
ipw.increaseIndent();
ipw.println(sb);
ipw.decreaseIndent();
@@ -267,6 +269,12 @@ class AggregatedPowerStats {
}
}
+ private static String formatDateTime(long timeInMillis) {
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
+ format.getCalendar().setTimeZone(TimeZone.getTimeZone("GMT"));
+ return format.format(new Date(timeInMillis));
+ }
+
@Override
public String toString() {
StringWriter sw = new StringWriter();
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
index 6fbbc0f072e8..5aad570ffd41 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -75,7 +75,7 @@ public class AggregatedPowerStatsConfig {
private final int mPowerComponentId;
private @TrackedState int[] mTrackedDeviceStates;
private @TrackedState int[] mTrackedUidStates;
- private AggregatedPowerStatsProcessor mProcessor = NO_OP_PROCESSOR;
+ private PowerStatsProcessor mProcessor = NO_OP_PROCESSOR;
PowerComponent(int powerComponentId) {
this.mPowerComponentId = powerComponentId;
@@ -85,6 +85,9 @@ public class AggregatedPowerStatsConfig {
* Configures which states should be tracked as separate dimensions for the entire device.
*/
public PowerComponent trackDeviceStates(@TrackedState int... states) {
+ if (mTrackedDeviceStates != null) {
+ throw new IllegalStateException("Component is already configured");
+ }
mTrackedDeviceStates = states;
return this;
}
@@ -93,6 +96,9 @@ public class AggregatedPowerStatsConfig {
* Configures which states should be tracked as separate dimensions on a per-UID basis.
*/
public PowerComponent trackUidStates(@TrackedState int... states) {
+ if (mTrackedUidStates != null) {
+ throw new IllegalStateException("Component is already configured");
+ }
mTrackedUidStates = states;
return this;
}
@@ -102,7 +108,7 @@ public class AggregatedPowerStatsConfig {
* before giving the aggregates stats to consumers. The processor can complete the
* aggregation process, for example by computing estimated power usage.
*/
- public PowerComponent setProcessor(@NonNull AggregatedPowerStatsProcessor processor) {
+ public PowerComponent setProcessor(@NonNull PowerStatsProcessor processor) {
mProcessor = processor;
return this;
}
@@ -137,7 +143,7 @@ public class AggregatedPowerStatsConfig {
}
@NonNull
- public AggregatedPowerStatsProcessor getProcessor() {
+ public PowerStatsProcessor getProcessor() {
return mProcessor;
}
@@ -153,6 +159,7 @@ public class AggregatedPowerStatsConfig {
}
return false;
}
+
}
private final List<PowerComponent> mPowerComponents = new ArrayList<>();
@@ -168,23 +175,55 @@ public class AggregatedPowerStatsConfig {
return builder;
}
+ /**
+ * Creates a configuration for the specified power component, which is a subcomponent
+ * of a different power component. The tracked states will be the same as the parent
+ * component's.
+ */
+ public PowerComponent trackPowerComponent(int powerComponentId,
+ int parentPowerComponentId) {
+ PowerComponent parent = null;
+ for (int i = 0; i < mPowerComponents.size(); i++) {
+ PowerComponent powerComponent = mPowerComponents.get(i);
+ if (powerComponent.getPowerComponentId() == parentPowerComponentId) {
+ parent = powerComponent;
+ break;
+ }
+ }
+
+ if (parent == null) {
+ throw new IllegalArgumentException(
+ "Parent component " + parentPowerComponentId + " is not configured");
+ }
+
+ PowerComponent powerComponent = trackPowerComponent(powerComponentId);
+ powerComponent.mTrackedDeviceStates = parent.mTrackedDeviceStates;
+ powerComponent.mTrackedUidStates = parent.mTrackedUidStates;
+ return powerComponent;
+ }
+
public List<PowerComponent> getPowerComponentsAggregatedStatsConfigs() {
return mPowerComponents;
}
- private static final AggregatedPowerStatsProcessor NO_OP_PROCESSOR =
- new AggregatedPowerStatsProcessor() {
+ private static final PowerStatsProcessor NO_OP_PROCESSOR =
+ new PowerStatsProcessor() {
@Override
- public void finish(PowerComponentAggregatedPowerStats stats) {
+ void finish(PowerComponentAggregatedPowerStats stats) {
}
@Override
- public String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+ String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
return Arrays.toString(stats);
}
@Override
- public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+ String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
+ return descriptor.getStateLabel(key) + " " + Arrays.toString(stats);
+ }
+
+ @Override
+ String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
return Arrays.toString(stats);
}
};
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index a8eda3ca6a47..cb10da9787df 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -24,6 +24,7 @@ import android.hardware.power.stats.EnergyConsumer;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyConsumerType;
import android.net.wifi.WifiManager;
+import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.Bundle;
import android.os.OutcomeReceiver;
@@ -603,24 +604,31 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat
}
if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) {
- // We were asked to fetch Telephony data.
- if (mTelephony != null) {
- CompletableFuture<ModemActivityInfo> temp = new CompletableFuture<>();
- mTelephony.requestModemActivityInfo(Runnable::run,
- new OutcomeReceiver<ModemActivityInfo,
- TelephonyManager.ModemActivityInfoException>() {
- @Override
- public void onResult(ModemActivityInfo result) {
- temp.complete(result);
- }
-
- @Override
- public void onError(TelephonyManager.ModemActivityInfoException e) {
- Slog.w(TAG, "error reading modem stats:" + e);
- temp.complete(null);
- }
- });
- modemFuture = temp;
+ @SuppressWarnings("GuardedBy")
+ PowerStatsCollector collector = mStats.getPowerStatsCollector(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO);
+ if (collector.isEnabled()) {
+ collector.schedule();
+ } else {
+ // We were asked to fetch Telephony data.
+ if (mTelephony != null) {
+ CompletableFuture<ModemActivityInfo> temp = new CompletableFuture<>();
+ mTelephony.requestModemActivityInfo(Runnable::run,
+ new OutcomeReceiver<ModemActivityInfo,
+ TelephonyManager.ModemActivityInfoException>() {
+ @Override
+ public void onResult(ModemActivityInfo result) {
+ temp.complete(result);
+ }
+
+ @Override
+ public void onError(TelephonyManager.ModemActivityInfoException e) {
+ Slog.w(TAG, "error reading modem stats:" + e);
+ temp.complete(null);
+ }
+ });
+ modemFuture = temp;
+ }
}
if (!railUpdated) {
synchronized (mStats) {
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 3a84897839a1..fc2df578c6e3 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -22,6 +22,8 @@ import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
+import static com.android.server.power.stats.MobileRadioPowerStatsCollector.mapRadioAccessNetworkTypeToRadioAccessTechnology;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -35,6 +37,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.hardware.usb.UsbManager;
import android.location.GnssSignalQuality;
@@ -71,6 +74,7 @@ import android.os.connectivity.CellularBatteryStats;
import android.os.connectivity.GpsBatteryStats;
import android.os.connectivity.WifiActivityEnergyInfo;
import android.os.connectivity.WifiBatteryStats;
+import android.power.PowerStatsInternal;
import android.provider.Settings;
import android.telephony.AccessNetworkConstants;
import android.telephony.Annotation.NetworkType;
@@ -99,6 +103,7 @@ import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SparseDoubleArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
@@ -137,6 +142,7 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.LocalServices;
import com.android.server.power.optimization.Flags;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
@@ -166,8 +172,12 @@ import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.IntSupplier;
+import java.util.function.LongSupplier;
+import java.util.function.Supplier;
/**
* All information we are collecting about things that can happen that impact
@@ -280,8 +290,9 @@ public class BatteryStatsImpl extends BatteryStats {
private KernelMemoryBandwidthStats mKernelMemoryBandwidthStats;
private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
private int[] mCpuPowerBracketMap;
- private final CpuPowerStatsCollector mCpuPowerStatsCollector;
- private boolean mPowerStatsCollectorEnabled;
+ private CpuPowerStatsCollector mCpuPowerStatsCollector;
+ private MobileRadioPowerStatsCollector mMobileRadioPowerStatsCollector;
+ private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray();
public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
return mKernelMemoryStats;
@@ -433,9 +444,11 @@ public class BatteryStatsImpl extends BatteryStats {
public static class BatteryStatsConfig {
static final int RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG = 1 << 0;
static final int RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG = 1 << 1;
+ static final long DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD =
+ TimeUnit.HOURS.toMillis(1);
private final int mFlags;
- private final long mPowerStatsThrottlePeriodCpu;
+ private SparseLongArray mPowerStatsThrottlePeriods;
private BatteryStatsConfig(Builder builder) {
int flags = 0;
@@ -446,7 +459,7 @@ public class BatteryStatsImpl extends BatteryStats {
flags |= RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG;
}
mFlags = flags;
- mPowerStatsThrottlePeriodCpu = builder.mPowerStatsThrottlePeriodCpu;
+ mPowerStatsThrottlePeriods = builder.mPowerStatsThrottlePeriods;
}
/**
@@ -467,8 +480,9 @@ public class BatteryStatsImpl extends BatteryStats {
== RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG;
}
- long getPowerStatsThrottlePeriodCpu() {
- return mPowerStatsThrottlePeriodCpu;
+ long getPowerStatsThrottlePeriod(@BatteryConsumer.PowerComponent int powerComponent) {
+ return mPowerStatsThrottlePeriods.get(powerComponent,
+ DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD);
}
/**
@@ -477,12 +491,16 @@ public class BatteryStatsImpl extends BatteryStats {
public static class Builder {
private boolean mResetOnUnplugHighBatteryLevel;
private boolean mResetOnUnplugAfterSignificantCharge;
- private long mPowerStatsThrottlePeriodCpu;
+ private SparseLongArray mPowerStatsThrottlePeriods;
public Builder() {
mResetOnUnplugHighBatteryLevel = true;
mResetOnUnplugAfterSignificantCharge = true;
- mPowerStatsThrottlePeriodCpu = 60000;
+ mPowerStatsThrottlePeriods = new SparseLongArray();
+ setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU,
+ TimeUnit.MINUTES.toMillis(1));
+ setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ TimeUnit.HOURS.toMillis(1));
}
/**
@@ -512,10 +530,11 @@ public class BatteryStatsImpl extends BatteryStats {
/**
* Sets the minimum amount of time (in millis) to wait between passes
- * of CPU power stats collection.
+ * of power stats collection for the specified power component.
*/
- public Builder setPowerStatsThrottlePeriodCpu(long periodMs) {
- mPowerStatsThrottlePeriodCpu = periodMs;
+ public Builder setPowerStatsThrottlePeriodMillis(
+ @BatteryConsumer.PowerComponent int powerComponent, long periodMs) {
+ mPowerStatsThrottlePeriods.put(powerComponent, periodMs);
return this;
}
}
@@ -597,7 +616,7 @@ public class BatteryStatsImpl extends BatteryStats {
@SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter
@VisibleForTesting
public void updateProcStateCpuTimesLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
- if (mPowerStatsCollectorEnabled) {
+ if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)) {
return;
}
@@ -653,7 +672,7 @@ public class BatteryStatsImpl extends BatteryStats {
*/
@SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter
public void updateCpuTimesForAllUids() {
- if (mPowerStatsCollectorEnabled && mCpuPowerStatsCollector != null) {
+ if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)) {
mCpuPowerStatsCollector.schedule();
return;
}
@@ -713,7 +732,8 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
private void ensureKernelSingleUidTimeReaderLocked() {
- if (mPowerStatsCollectorEnabled || mKernelSingleUidTimeReader != null) {
+ if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)
+ || mKernelSingleUidTimeReader != null) {
return;
}
@@ -830,8 +850,6 @@ public class BatteryStatsImpl extends BatteryStats {
private final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
private final HistoryStepDetailsCalculatorImpl mStepDetailsCalculator =
new HistoryStepDetailsCalculatorImpl();
- private final PowerStats.DescriptorRegistry mPowerStatsDescriptorRegistry =
- new PowerStats.DescriptorRegistry();
private boolean mHaveBatteryLevel = false;
private boolean mBatteryPluggedIn;
@@ -1838,19 +1856,28 @@ public class BatteryStatsImpl extends BatteryStats {
FrameworkStatsLog.write(
FrameworkStatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin);
}
+
+ /**
+ * Records a statsd event when the batterystats config file is written to disk.
+ */
+ public void writeCommitSysConfigFile(String fileName, long durationMs) {
+ com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(fileName,
+ durationMs);
+ }
}
private final FrameworkStatsLogger mFrameworkStatsLogger;
@VisibleForTesting
- public BatteryStatsImpl(Clock clock, File historyDirectory, @NonNull Handler handler,
+ public BatteryStatsImpl(@NonNull BatteryStatsConfig config, Clock clock, File historyDirectory,
+ @NonNull Handler handler,
@NonNull PowerStatsUidResolver powerStatsUidResolver,
@NonNull FrameworkStatsLogger frameworkStatsLogger,
@NonNull BatteryStatsHistory.TraceDelegate traceDelegate,
@NonNull BatteryStatsHistory.EventLogger eventLogger) {
+ mBatteryStatsConfig = config;
mClock = clock;
initKernelStatsReaders();
- mBatteryStatsConfig = new BatteryStatsConfig.Builder().build();
mHandler = handler;
mPowerStatsUidResolver = powerStatsUidResolver;
mFrameworkStatsLogger = frameworkStatsLogger;
@@ -1873,7 +1900,7 @@ public class BatteryStatsImpl extends BatteryStats {
mPlatformIdleStateCallback = null;
mEnergyConsumerRetriever = null;
mUserInfoProvider = null;
- mCpuPowerStatsCollector = null;
+ initPowerStatsCollectors();
}
private void initKernelStatsReaders() {
@@ -1893,6 +1920,105 @@ public class BatteryStatsImpl extends BatteryStats {
mTmpRailStats = new RailStats();
}
+ private class PowerStatsCollectorInjector implements CpuPowerStatsCollector.Injector,
+ MobileRadioPowerStatsCollector.Injector {
+ private PackageManager mPackageManager;
+ private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ private NetworkStatsManager mNetworkStatsManager;
+ private TelephonyManager mTelephonyManager;
+
+ void setContext(Context context) {
+ mPackageManager = context.getPackageManager();
+ mConsumedEnergyRetriever = new PowerStatsCollector.ConsumedEnergyRetrieverImpl(
+ LocalServices.getService(PowerStatsInternal.class));
+ mNetworkStatsManager = context.getSystemService(NetworkStatsManager.class);
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ }
+
+ @Override
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ @Override
+ public Clock getClock() {
+ return mClock;
+ }
+
+ @Override
+ public PowerStatsUidResolver getUidResolver() {
+ return mPowerStatsUidResolver;
+ }
+
+ @Override
+ public CpuScalingPolicies getCpuScalingPolicies() {
+ return mCpuScalingPolicies;
+ }
+
+ @Override
+ public PowerProfile getPowerProfile() {
+ return mPowerProfile;
+ }
+
+ @Override
+ public CpuPowerStatsCollector.KernelCpuStatsReader getKernelCpuStatsReader() {
+ return new CpuPowerStatsCollector.KernelCpuStatsReader();
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ @Override
+ public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+ return mConsumedEnergyRetriever;
+ }
+
+ @Override
+ public IntSupplier getVoltageSupplier() {
+ return () -> mBatteryVoltageMv;
+ }
+
+ @Override
+ public Supplier<NetworkStats> getMobileNetworkStatsSupplier() {
+ return () -> readMobileNetworkStatsLocked(mNetworkStatsManager);
+ }
+
+ @Override
+ public TelephonyManager getTelephonyManager() {
+ return mTelephonyManager;
+ }
+
+ @Override
+ public LongSupplier getCallDurationSupplier() {
+ return () -> mPhoneOnTimer.getTotalTimeLocked(mClock.elapsedRealtime() * 1000,
+ STATS_SINCE_CHARGED);
+ }
+
+ @Override
+ public LongSupplier getPhoneSignalScanDurationSupplier() {
+ return () -> mPhoneSignalScanningTimer.getTotalTimeLocked(
+ mClock.elapsedRealtime() * 1000, STATS_SINCE_CHARGED);
+ }
+ }
+
+ private final PowerStatsCollectorInjector mPowerStatsCollectorInjector =
+ new PowerStatsCollectorInjector();
+
+ @SuppressWarnings("GuardedBy") // Accessed from constructor only
+ private void initPowerStatsCollectors() {
+ mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector,
+ mBatteryStatsConfig.getPowerStatsThrottlePeriod(
+ BatteryConsumer.POWER_COMPONENT_CPU));
+ mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
+
+ mMobileRadioPowerStatsCollector = new MobileRadioPowerStatsCollector(
+ mPowerStatsCollectorInjector, mBatteryStatsConfig.getPowerStatsThrottlePeriod(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO));
+ mMobileRadioPowerStatsCollector.addConsumer(this::recordPowerStats);
+ }
+
/**
* TimeBase observer.
*/
@@ -5738,16 +5864,19 @@ public class BatteryStatsImpl extends BatteryStats {
mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs);
mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs);
- if (mLastModemActivityInfo != null) {
- if (elapsedRealtimeMs < mLastModemActivityInfo.getTimestampMillis()
+ if (mMobileRadioPowerStatsCollector.isEnabled()) {
+ mMobileRadioPowerStatsCollector.schedule();
+ } else {
+ // Check if modem Activity info has been collected recently, don't bother
+ // triggering another update.
+ if (mLastModemActivityInfo == null
+ || elapsedRealtimeMs >= mLastModemActivityInfo.getTimestampMillis()
+ MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS) {
- // Modem Activity info has been collected recently, don't bother
- // triggering another update.
- return false;
+ mExternalSync.scheduleSync("modem-data",
+ BatteryExternalStatsWorker.UPDATE_RADIO);
+ return true;
}
}
- // Tell the caller to collect radio network/power stats.
- return true;
}
}
return false;
@@ -5915,6 +6044,7 @@ public class BatteryStatsImpl extends BatteryStats {
mPhoneOnTimer.startRunningLocked(elapsedRealtimeMs);
if (mConstants.PHONE_ON_EXTERNAL_STATS_COLLECTION) {
scheduleSyncExternalStatsLocked("phone-on", ExternalStatsSync.UPDATE_RADIO);
+ mMobileRadioPowerStatsCollector.schedule();
}
}
}
@@ -5927,6 +6057,7 @@ public class BatteryStatsImpl extends BatteryStats {
mPhoneOn = false;
mPhoneOnTimer.stopRunningLocked(elapsedRealtimeMs);
scheduleSyncExternalStatsLocked("phone-off", ExternalStatsSync.UPDATE_RADIO);
+ mMobileRadioPowerStatsCollector.schedule();
}
}
@@ -6269,27 +6400,6 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
- @RadioAccessTechnology
- private static int mapRadioAccessNetworkTypeToRadioAccessTechnology(
- @AccessNetworkConstants.RadioAccessNetworkType int dataType) {
- switch (dataType) {
- case AccessNetworkConstants.AccessNetworkType.NGRAN:
- return RADIO_ACCESS_TECHNOLOGY_NR;
- case AccessNetworkConstants.AccessNetworkType.EUTRAN:
- return RADIO_ACCESS_TECHNOLOGY_LTE;
- case AccessNetworkConstants.AccessNetworkType.UNKNOWN: //fallthrough
- case AccessNetworkConstants.AccessNetworkType.GERAN: //fallthrough
- case AccessNetworkConstants.AccessNetworkType.UTRAN: //fallthrough
- case AccessNetworkConstants.AccessNetworkType.CDMA2000: //fallthrough
- case AccessNetworkConstants.AccessNetworkType.IWLAN:
- return RADIO_ACCESS_TECHNOLOGY_OTHER;
- default:
- Slog.w(TAG,
- "Unhandled RadioAccessNetworkType (" + dataType + "), mapping to OTHER");
- return RADIO_ACCESS_TECHNOLOGY_OTHER;
- }
- }
-
@GuardedBy("this")
public void noteWifiOnLocked(long elapsedRealtimeMs, long uptimeMs) {
if (!mWifiOn) {
@@ -8311,7 +8421,7 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("mBsi")
private void ensureMultiStateCounters(long timestampMs) {
- if (mBsi.mPowerStatsCollectorEnabled) {
+ if (mBsi.mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)) {
throw new IllegalStateException("Multi-state counters used in streamlined mode");
}
@@ -10612,7 +10722,8 @@ public class BatteryStatsImpl extends BatteryStats {
mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtimeMs);
}
- if (!mBsi.mPowerStatsCollectorEnabled && mBsi.trackPerProcStateCpuTimes()) {
+ if (!mBsi.mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)
+ && mBsi.trackPerProcStateCpuTimes()) {
mBsi.updateProcStateCpuTimesLocked(mUid, elapsedRealtimeMs, uptimeMs);
LongArrayMultiStateCounter onBatteryCounter =
@@ -10634,7 +10745,8 @@ public class BatteryStatsImpl extends BatteryStats {
final int batteryConsumerProcessState =
mapUidProcessStateToBatteryConsumerProcessState(uidRunningState);
- if (mBsi.mSystemReady && mBsi.mPowerStatsCollectorEnabled) {
+ if (mBsi.mSystemReady && mBsi.mPowerStatsCollectorEnabled.get(
+ BatteryConsumer.POWER_COMPONENT_CPU)) {
mBsi.mHistory.recordProcessStateChange(elapsedRealtimeMs, uptimeMs, mUid,
batteryConsumerProcessState);
}
@@ -11016,11 +11128,7 @@ public class BatteryStatsImpl extends BatteryStats {
mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock);
}
- mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile,
- mPowerStatsUidResolver, () -> mBatteryVoltageMv, mHandler,
- mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu());
- mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
-
+ initPowerStatsCollectors();
mStartCount++;
initTimersAndCounters();
mOnBattery = mOnBatteryInternal = false;
@@ -11296,8 +11404,7 @@ public class BatteryStatsImpl extends BatteryStats {
memStream.writeTo(stream);
stream.flush();
mDailyFile.finishWrite(stream);
- com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
- "batterystats-daily",
+ mFrameworkStatsLogger.writeCommitSysConfigFile("batterystats-daily",
initialTimeMs + SystemClock.uptimeMillis() - startTimeMs2);
} catch (IOException e) {
Slog.w("BatteryStats",
@@ -11809,7 +11916,7 @@ public class BatteryStatsImpl extends BatteryStats {
// Store the empty state to disk to ensure consistency
writeSyncLocked();
- if (mPowerStatsCollectorEnabled) {
+ if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)) {
schedulePowerStatsSampleCollection();
}
@@ -11953,7 +12060,7 @@ public class BatteryStatsImpl extends BatteryStats {
return networkStatsManager.getWifiUidStats();
}
- private static class NetworkStatsDelta {
+ static class NetworkStatsDelta {
int mUid;
int mSet;
long mRxBytes;
@@ -11985,9 +12092,16 @@ public class BatteryStatsImpl extends BatteryStats {
public long getTxPackets() {
return mTxPackets;
}
+
+ @Override
+ public String toString() {
+ return "NetworkStatsDelta{mUid=" + mUid + ", mSet=" + mSet + ", mRxBytes=" + mRxBytes
+ + ", mRxPackets=" + mRxPackets + ", mTxBytes=" + mTxBytes + ", mTxPackets="
+ + mTxPackets + '}';
+ }
}
- private List<NetworkStatsDelta> computeDelta(NetworkStats currentStats,
+ static List<NetworkStatsDelta> computeDelta(NetworkStats currentStats,
NetworkStats lastStats) {
List<NetworkStatsDelta> deltaList = new ArrayList<>();
for (NetworkStats.Entry entry : currentStats) {
@@ -12418,13 +12532,11 @@ public class BatteryStatsImpl extends BatteryStats {
addModemTxPowerToHistory(deltaInfo, elapsedRealtimeMs, uptimeMs);
// Grab a separate lock to acquire the network stats, which may do I/O.
- NetworkStats delta = null;
+ List<NetworkStatsDelta> delta = null;
synchronized (mModemNetworkLock) {
final NetworkStats latestStats = readMobileNetworkStatsLocked(networkStatsManager);
if (latestStats != null) {
- delta = latestStats.subtract(mLastModemNetworkStats != null
- ? mLastModemNetworkStats
- : new NetworkStats(0, -1));
+ delta = computeDelta(latestStats, mLastModemNetworkStats);
mLastModemNetworkStats = latestStats;
}
}
@@ -12527,7 +12639,7 @@ public class BatteryStatsImpl extends BatteryStats {
long totalRxPackets = 0;
long totalTxPackets = 0;
if (delta != null) {
- for (NetworkStats.Entry entry : delta) {
+ for (NetworkStatsDelta entry : delta) {
if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
continue;
}
@@ -12568,7 +12680,7 @@ public class BatteryStatsImpl extends BatteryStats {
// Now distribute proportional blame to the apps that did networking.
long totalPackets = totalRxPackets + totalTxPackets;
if (totalPackets > 0) {
- for (NetworkStats.Entry entry : delta) {
+ for (NetworkStatsDelta entry : delta) {
if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
continue;
}
@@ -14408,17 +14520,41 @@ public class BatteryStatsImpl extends BatteryStats {
/**
* Notifies BatteryStatsImpl that the system server is ready.
*/
- public void onSystemReady() {
+ public void onSystemReady(Context context) {
if (mCpuUidFreqTimeReader != null) {
mCpuUidFreqTimeReader.onSystemReady();
}
- if (mCpuPowerStatsCollector != null) {
- mCpuPowerStatsCollector.setEnabled(mPowerStatsCollectorEnabled);
- }
+
+ mPowerStatsCollectorInjector.setContext(context);
+
+ mCpuPowerStatsCollector.setEnabled(
+ mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU));
+ mCpuPowerStatsCollector.schedule();
+
+ mMobileRadioPowerStatsCollector.setEnabled(
+ mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO));
+ mMobileRadioPowerStatsCollector.schedule();
+
mSystemReady = true;
}
/**
+ * Returns a PowerStatsCollector for the specified power component or null if unavailable.
+ */
+ @Nullable
+ PowerStatsCollector getPowerStatsCollector(
+ @BatteryConsumer.PowerComponent int powerComponent) {
+ switch (powerComponent) {
+ case BatteryConsumer.POWER_COMPONENT_CPU:
+ return mCpuPowerStatsCollector;
+ case BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO:
+ return mMobileRadioPowerStatsCollector;
+ }
+ return null;
+ }
+
+
+ /**
* Force recording of all history events regardless of the "charging" state.
*/
@VisibleForTesting
@@ -14561,9 +14697,10 @@ public class BatteryStatsImpl extends BatteryStats {
stream.write(parcel.marshall());
stream.flush();
mCheckinFile.finishWrite(stream);
- com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
- "batterystats-checkin", initialTimeMs
- + SystemClock.uptimeMillis() - startTimeMs2);
+ mFrameworkStatsLogger.writeCommitSysConfigFile(
+ "batterystats-checkin",
+ initialTimeMs + SystemClock.uptimeMillis()
+ - startTimeMs2);
} catch (IOException e) {
Slog.w("BatteryStats",
"Error writing checkin battery statistics", e);
@@ -15437,9 +15574,10 @@ public class BatteryStatsImpl extends BatteryStats {
/**
* Enables or disables the PowerStatsCollector mode.
*/
- public void setPowerStatsCollectorEnabled(boolean enabled) {
+ public void setPowerStatsCollectorEnabled(@BatteryConsumer.PowerComponent int powerComponent,
+ boolean enabled) {
synchronized (this) {
- mPowerStatsCollectorEnabled = enabled;
+ mPowerStatsCollectorEnabled.put(powerComponent, enabled);
}
}
@@ -15944,10 +16082,8 @@ public class BatteryStatsImpl extends BatteryStats {
* Callers will need to wait for the collection to complete on the handler thread.
*/
public void schedulePowerStatsSampleCollection() {
- if (mCpuPowerStatsCollector == null) {
- return;
- }
mCpuPowerStatsCollector.forceSchedule();
+ mMobileRadioPowerStatsCollector.forceSchedule();
}
/**
@@ -15965,6 +16101,7 @@ public class BatteryStatsImpl extends BatteryStats {
*/
public void dumpStatsSample(PrintWriter pw) {
mCpuPowerStatsCollector.collectAndDump(pw);
+ mMobileRadioPowerStatsCollector.collectAndDump(pw);
}
private final Runnable mWriteAsyncRunnable = () -> {
@@ -16036,7 +16173,7 @@ public class BatteryStatsImpl extends BatteryStats {
+ " duration ms:" + (SystemClock.uptimeMillis() - startTimeMs)
+ " bytes:" + p.dataSize());
}
- com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
+ mFrameworkStatsLogger.writeCommitSysConfigFile(
"batterystats", SystemClock.uptimeMillis() - startTimeMs);
} catch (IOException e) {
Slog.w(TAG, "Error writing battery statistics", e);
@@ -17262,10 +17399,8 @@ public class BatteryStatsImpl extends BatteryStats {
pw.println();
dumpConstantsLocked(pw);
- if (mCpuPowerStatsCollector != null) {
- pw.println();
- mCpuPowerStatsCollector.dumpCpuPowerBracketsLocked(pw);
- }
+ pw.println();
+ mCpuPowerStatsCollector.dumpCpuPowerBracketsLocked(pw);
pw.println();
dumpEnergyConsumerStatsLocked(pw);
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 30b80ae781ff..97f09865beeb 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -27,6 +27,7 @@ import android.os.UidBatteryConsumer;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.internal.os.Clock;
import com.android.internal.os.CpuScalingPolicies;
@@ -43,7 +44,7 @@ import java.util.List;
public class BatteryUsageStatsProvider {
private static final String TAG = "BatteryUsageStatsProv";
private final Context mContext;
- private boolean mPowerStatsExporterEnabled;
+ private final SparseBooleanArray mPowerStatsExporterEnabled = new SparseBooleanArray();
private final PowerStatsExporter mPowerStatsExporter;
private final PowerStatsStore mPowerStatsStore;
private final PowerProfile mPowerProfile;
@@ -71,14 +72,20 @@ public class BatteryUsageStatsProvider {
// Power calculators are applied in the order of registration
mPowerCalculators.add(new BatteryChargeCalculator());
- if (!mPowerStatsExporterEnabled) {
+ if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)) {
mPowerCalculators.add(
new CpuPowerCalculator(mCpuScalingPolicies, mPowerProfile));
}
mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
if (!BatteryStats.checkWifiOnly(mContext)) {
- mPowerCalculators.add(new MobileRadioPowerCalculator(mPowerProfile));
+ if (!mPowerStatsExporterEnabled.get(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) {
+ mPowerCalculators.add(new MobileRadioPowerCalculator(mPowerProfile));
+ }
+ if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_PHONE)) {
+ mPowerCalculators.add(new PhonePowerCalculator(mPowerProfile));
+ }
}
mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile));
mPowerCalculators.add(new BluetoothPowerCalculator(mPowerProfile));
@@ -89,7 +96,6 @@ public class BatteryUsageStatsProvider {
mPowerCalculators.add(new FlashlightPowerCalculator(mPowerProfile));
mPowerCalculators.add(new AudioPowerCalculator(mPowerProfile));
mPowerCalculators.add(new VideoPowerCalculator(mPowerProfile));
- mPowerCalculators.add(new PhonePowerCalculator(mPowerProfile));
mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile));
mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
@@ -228,7 +234,7 @@ public class BatteryUsageStatsProvider {
}
}
- if (mPowerStatsExporterEnabled) {
+ if (mPowerStatsExporterEnabled.indexOfValue(true) >= 0) {
mPowerStatsExporter.exportAggregatedPowerStats(batteryUsageStatsBuilder,
monotonicStartTime, monotonicEndTime);
}
@@ -393,7 +399,10 @@ public class BatteryUsageStatsProvider {
return builder.build();
}
- public void setPowerStatsExporterEnabled(boolean enabled) {
- mPowerStatsExporterEnabled = enabled;
+ /**
+ * Specify whether PowerStats based attribution is supported for the specified component.
+ */
+ public void setPowerStatsExporterEnabled(int powerComponentId, boolean enabled) {
+ mPowerStatsExporterEnabled.put(powerComponentId, enabled);
}
}
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index 1af127175f80..b1b2cc91d379 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -16,14 +16,11 @@
package com.android.server.power.stats;
-import android.hardware.power.stats.EnergyConsumer;
-import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyConsumerType;
import android.os.BatteryConsumer;
import android.os.Handler;
import android.os.PersistableBundle;
import android.os.Process;
-import android.power.PowerStatsInternal;
import android.util.Slog;
import android.util.SparseArray;
@@ -34,20 +31,11 @@ import com.android.internal.os.Clock;
import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.PowerStats;
-import com.android.server.LocalServices;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
import java.util.Locale;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import java.util.function.IntSupplier;
-import java.util.function.Supplier;
/**
* Collects snapshots of power-related system statistics.
@@ -63,213 +51,54 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
private static final int DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER = 2;
private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
+ interface Injector {
+ Handler getHandler();
+ Clock getClock();
+ PowerStatsUidResolver getUidResolver();
+ CpuScalingPolicies getCpuScalingPolicies();
+ PowerProfile getPowerProfile();
+ KernelCpuStatsReader getKernelCpuStatsReader();
+ ConsumedEnergyRetriever getConsumedEnergyRetriever();
+ IntSupplier getVoltageSupplier();
+
+ default int getDefaultCpuPowerBrackets() {
+ return DEFAULT_CPU_POWER_BRACKETS;
+ }
+
+ default int getDefaultCpuPowerBracketsPerEnergyConsumer() {
+ return DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER;
+ }
+ }
+
+ private final Injector mInjector;
+
private boolean mIsInitialized;
- private final CpuScalingPolicies mCpuScalingPolicies;
- private final PowerProfile mPowerProfile;
- private final KernelCpuStatsReader mKernelCpuStatsReader;
- private final PowerStatsUidResolver mUidResolver;
- private final Supplier<PowerStatsInternal> mPowerStatsSupplier;
- private final IntSupplier mVoltageSupplier;
- private final int mDefaultCpuPowerBrackets;
- private final int mDefaultCpuPowerBracketsPerEnergyConsumer;
+ private CpuScalingPolicies mCpuScalingPolicies;
+ private PowerProfile mPowerProfile;
+ private KernelCpuStatsReader mKernelCpuStatsReader;
+ private PowerStatsUidResolver mUidResolver;
+ private ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ private IntSupplier mVoltageSupplier;
+ private int mDefaultCpuPowerBrackets;
+ private int mDefaultCpuPowerBracketsPerEnergyConsumer;
private long[] mCpuTimeByScalingStep;
private long[] mTempCpuTimeByScalingStep;
private long[] mTempUidStats;
private final SparseArray<UidStats> mUidStats = new SparseArray<>();
private boolean mIsPerUidTimeInStateSupported;
- private PowerStatsInternal mPowerStatsInternal;
private int[] mCpuEnergyConsumerIds = new int[0];
private PowerStats.Descriptor mPowerStatsDescriptor;
// Reusable instance
private PowerStats mCpuPowerStats;
- private CpuStatsArrayLayout mLayout;
+ private CpuPowerStatsLayout mLayout;
private long mLastUpdateTimestampNanos;
private long mLastUpdateUptimeMillis;
private int mLastVoltageMv;
private long[] mLastConsumedEnergyUws;
- /**
- * Captures the positions and lengths of sections of the stats array, such as time-in-state,
- * power usage estimates etc.
- */
- public static class CpuStatsArrayLayout extends StatsArrayLayout {
- private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION = "dt";
- private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT = "dtc";
- private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION = "dc";
- private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT = "dcc";
- private static final String EXTRA_UID_BRACKETS_POSITION = "ub";
- private static final String EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET = "us";
-
- private int mDeviceCpuTimeByScalingStepPosition;
- private int mDeviceCpuTimeByScalingStepCount;
- private int mDeviceCpuTimeByClusterPosition;
- private int mDeviceCpuTimeByClusterCount;
-
- private int mUidPowerBracketsPosition;
- private int mUidPowerBracketCount;
-
- private int[] mScalingStepToPowerBracketMap;
-
- /**
- * Declare that the stats array has a section capturing CPU time per scaling step
- */
- public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) {
- mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount);
- mDeviceCpuTimeByScalingStepCount = scalingStepCount;
- }
-
- public int getCpuScalingStepCount() {
- return mDeviceCpuTimeByScalingStepCount;
- }
-
- /**
- * Saves the time duration in the <code>stats</code> element
- * corresponding to the CPU scaling <code>state</code>.
- */
- public void setTimeByScalingStep(long[] stats, int step, long value) {
- stats[mDeviceCpuTimeByScalingStepPosition + step] = value;
- }
-
- /**
- * Extracts the time duration from the <code>stats</code> element
- * corresponding to the CPU scaling <code>step</code>.
- */
- public long getTimeByScalingStep(long[] stats, int step) {
- return stats[mDeviceCpuTimeByScalingStepPosition + step];
- }
-
- /**
- * Declare that the stats array has a section capturing CPU time in each cluster
- */
- public void addDeviceSectionCpuTimeByCluster(int clusterCount) {
- mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount);
- mDeviceCpuTimeByClusterCount = clusterCount;
- }
-
- public int getCpuClusterCount() {
- return mDeviceCpuTimeByClusterCount;
- }
-
- /**
- * Saves the time duration in the <code>stats</code> element
- * corresponding to the CPU <code>cluster</code>.
- */
- public void setTimeByCluster(long[] stats, int cluster, long value) {
- stats[mDeviceCpuTimeByClusterPosition + cluster] = value;
- }
-
- /**
- * Extracts the time duration from the <code>stats</code> element
- * corresponding to the CPU <code>cluster</code>.
- */
- public long getTimeByCluster(long[] stats, int cluster) {
- return stats[mDeviceCpuTimeByClusterPosition + cluster];
- }
-
- /**
- * Declare that the UID stats array has a section capturing CPU time per power bracket.
- */
- public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) {
- mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap;
- updatePowerBracketCount();
- mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount);
- }
-
- private void updatePowerBracketCount() {
- mUidPowerBracketCount = 1;
- for (int bracket : mScalingStepToPowerBracketMap) {
- if (bracket >= mUidPowerBracketCount) {
- mUidPowerBracketCount = bracket + 1;
- }
- }
- }
-
- public int[] getScalingStepToPowerBracketMap() {
- return mScalingStepToPowerBracketMap;
- }
-
- public int getCpuPowerBracketCount() {
- return mUidPowerBracketCount;
- }
-
- /**
- * Saves time in <code>bracket</code> in the corresponding section of <code>stats</code>.
- */
- public void setUidTimeByPowerBracket(long[] stats, int bracket, long value) {
- stats[mUidPowerBracketsPosition + bracket] = value;
- }
-
- /**
- * Extracts the time in <code>bracket</code> from a UID stats array.
- */
- public long getUidTimeByPowerBracket(long[] stats, int bracket) {
- return stats[mUidPowerBracketsPosition + bracket];
- }
-
- /**
- * Copies the elements of the stats array layout into <code>extras</code>
- */
- public void toExtras(PersistableBundle extras) {
- super.toExtras(extras);
- extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION,
- mDeviceCpuTimeByScalingStepPosition);
- extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT,
- mDeviceCpuTimeByScalingStepCount);
- extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION,
- mDeviceCpuTimeByClusterPosition);
- extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT,
- mDeviceCpuTimeByClusterCount);
- extras.putInt(EXTRA_UID_BRACKETS_POSITION, mUidPowerBracketsPosition);
- putIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET,
- mScalingStepToPowerBracketMap);
- }
-
- /**
- * Retrieves elements of the stats array layout from <code>extras</code>
- */
- public void fromExtras(PersistableBundle extras) {
- super.fromExtras(extras);
- mDeviceCpuTimeByScalingStepPosition =
- extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION);
- mDeviceCpuTimeByScalingStepCount =
- extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT);
- mDeviceCpuTimeByClusterPosition =
- extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION);
- mDeviceCpuTimeByClusterCount =
- extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT);
- mUidPowerBracketsPosition = extras.getInt(EXTRA_UID_BRACKETS_POSITION);
- mScalingStepToPowerBracketMap =
- getIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET);
- if (mScalingStepToPowerBracketMap == null) {
- mScalingStepToPowerBracketMap = new int[mDeviceCpuTimeByScalingStepCount];
- }
- updatePowerBracketCount();
- }
- }
-
- public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile,
- PowerStatsUidResolver uidResolver, IntSupplier voltageSupplier, Handler handler,
- long throttlePeriodMs) {
- this(cpuScalingPolicies, powerProfile, handler, new KernelCpuStatsReader(), uidResolver,
- () -> LocalServices.getService(PowerStatsInternal.class), voltageSupplier,
- throttlePeriodMs, Clock.SYSTEM_CLOCK, DEFAULT_CPU_POWER_BRACKETS,
- DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER);
- }
-
- public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile,
- Handler handler, KernelCpuStatsReader kernelCpuStatsReader,
- PowerStatsUidResolver uidResolver, Supplier<PowerStatsInternal> powerStatsSupplier,
- IntSupplier voltageSupplier, long throttlePeriodMs, Clock clock,
- int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) {
- super(handler, throttlePeriodMs, clock);
- mCpuScalingPolicies = cpuScalingPolicies;
- mPowerProfile = powerProfile;
- mKernelCpuStatsReader = kernelCpuStatsReader;
- mUidResolver = uidResolver;
- mPowerStatsSupplier = powerStatsSupplier;
- mVoltageSupplier = voltageSupplier;
- mDefaultCpuPowerBrackets = defaultCpuPowerBrackets;
- mDefaultCpuPowerBracketsPerEnergyConsumer = defaultCpuPowerBracketsPerEnergyConsumer;
+ public CpuPowerStatsCollector(Injector injector, long throttlePeriodMs) {
+ super(injector.getHandler(), throttlePeriodMs, injector.getClock());
+ mInjector = injector;
}
private boolean ensureInitialized() {
@@ -281,19 +110,28 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
return false;
}
- mIsPerUidTimeInStateSupported = mKernelCpuStatsReader.nativeIsSupportedFeature();
- mPowerStatsInternal = mPowerStatsSupplier.get();
-
- if (mPowerStatsInternal != null) {
- readCpuEnergyConsumerIds();
- }
+ mCpuScalingPolicies = mInjector.getCpuScalingPolicies();
+ mPowerProfile = mInjector.getPowerProfile();
+ mKernelCpuStatsReader = mInjector.getKernelCpuStatsReader();
+ mUidResolver = mInjector.getUidResolver();
+ mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
+ mVoltageSupplier = mInjector.getVoltageSupplier();
+ mDefaultCpuPowerBrackets = mInjector.getDefaultCpuPowerBrackets();
+ mDefaultCpuPowerBracketsPerEnergyConsumer =
+ mInjector.getDefaultCpuPowerBracketsPerEnergyConsumer();
+
+ mIsPerUidTimeInStateSupported = mKernelCpuStatsReader.isSupportedFeature();
+ mCpuEnergyConsumerIds =
+ mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.CPU_CLUSTER);
+ mLastConsumedEnergyUws = new long[mCpuEnergyConsumerIds.length];
+ Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
int cpuScalingStepCount = mCpuScalingPolicies.getScalingStepCount();
mCpuTimeByScalingStep = new long[cpuScalingStepCount];
mTempCpuTimeByScalingStep = new long[cpuScalingStepCount];
int[] scalingStepToPowerBracketMap = initPowerBrackets();
- mLayout = new CpuStatsArrayLayout();
+ mLayout = new CpuPowerStatsLayout();
mLayout.addDeviceSectionCpuTimeByScalingStep(cpuScalingStepCount);
mLayout.addDeviceSectionCpuTimeByCluster(mCpuScalingPolicies.getPolicies().length);
mLayout.addDeviceSectionUsageDuration();
@@ -306,7 +144,8 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
mLayout.toExtras(extras);
mPowerStatsDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU,
- mLayout.getDeviceStatsArrayLength(), mLayout.getUidStatsArrayLength(), extras);
+ mLayout.getDeviceStatsArrayLength(), /* stateLabels */null,
+ /* stateStatsArrayLength */ 0, mLayout.getUidStatsArrayLength(), extras);
mCpuPowerStats = new PowerStats(mPowerStatsDescriptor);
mTempUidStats = new long[mLayout.getCpuPowerBracketCount()];
@@ -315,32 +154,6 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
return true;
}
- private void readCpuEnergyConsumerIds() {
- EnergyConsumer[] energyConsumerInfo = mPowerStatsInternal.getEnergyConsumerInfo();
- if (energyConsumerInfo == null) {
- return;
- }
-
- List<EnergyConsumer> cpuEnergyConsumers = new ArrayList<>();
- for (EnergyConsumer energyConsumer : energyConsumerInfo) {
- if (energyConsumer.type == EnergyConsumerType.CPU_CLUSTER) {
- cpuEnergyConsumers.add(energyConsumer);
- }
- }
- if (cpuEnergyConsumers.isEmpty()) {
- return;
- }
-
- cpuEnergyConsumers.sort(Comparator.comparing(c -> c.ordinal));
-
- mCpuEnergyConsumerIds = new int[cpuEnergyConsumers.size()];
- for (int i = 0; i < mCpuEnergyConsumerIds.length; i++) {
- mCpuEnergyConsumerIds[i] = cpuEnergyConsumers.get(i).id;
- }
- mLastConsumedEnergyUws = new long[cpuEnergyConsumers.size()];
- Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
- }
-
private int[] initPowerBrackets() {
if (mPowerProfile.getCpuPowerBracketCount() != PowerProfile.POWER_BRACKETS_UNSPECIFIED) {
return initPowerBracketsFromPowerProfile();
@@ -372,6 +185,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
return stepToBracketMap;
}
+
private int[] initPowerBracketsByCluster(int defaultBracketCountPerCluster) {
int[] stepToBracketMap = new int[mCpuScalingPolicies.getScalingStepCount()];
int index = 0;
@@ -531,7 +345,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
mCpuPowerStats.uidStats.clear();
// TODO(b/305120724): additionally retrieve time-in-cluster for each CPU cluster
- long newTimestampNanos = mKernelCpuStatsReader.nativeReadCpuStats(this::processUidStats,
+ long newTimestampNanos = mKernelCpuStatsReader.readCpuStats(this::processUidStats,
mLayout.getScalingStepToPowerBracketMap(), mLastUpdateTimestampNanos,
mTempCpuTimeByScalingStep, mTempUidStats);
for (int step = mLayout.getCpuScalingStepCount() - 1; step >= 0; step--) {
@@ -571,35 +385,20 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
mLastVoltageMv = voltageMv;
- CompletableFuture<EnergyConsumerResult[]> future =
- mPowerStatsInternal.getEnergyConsumedAsync(mCpuEnergyConsumerIds);
- EnergyConsumerResult[] results = null;
- try {
- results = future.get(
- POWER_STATS_ENERGY_CONSUMERS_TIMEOUT, TimeUnit.MILLISECONDS);
- } catch (InterruptedException | ExecutionException | TimeoutException e) {
- Slog.e(TAG, "Could not obtain energy consumers from PowerStatsService", e);
- }
- if (results == null) {
+ long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mCpuEnergyConsumerIds);
+ if (energyUws == null) {
return;
}
- for (int i = 0; i < mCpuEnergyConsumerIds.length; i++) {
- int id = mCpuEnergyConsumerIds[i];
- for (EnergyConsumerResult result : results) {
- if (result.id == id) {
- long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED
- ? result.energyUWs - mLastConsumedEnergyUws[i] : 0;
- if (energyDelta < 0) {
- // Likely, restart of powerstats HAL
- energyDelta = 0;
- }
- mLayout.setConsumedEnergy(mCpuPowerStats.stats, i,
- uJtoUc(energyDelta, averageVoltage));
- mLastConsumedEnergyUws[i] = result.energyUWs;
- break;
- }
+ for (int i = energyUws.length - 1; i >= 0; i--) {
+ long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED
+ ? energyUws[i] - mLastConsumedEnergyUws[i] : 0;
+ if (energyDelta < 0) {
+ // Likely, restart of powerstats HAL
+ energyDelta = 0;
}
+ mLayout.setConsumedEnergy(mCpuPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage));
+ mLastConsumedEnergyUws[i] = energyUws[i];
}
}
@@ -652,6 +451,17 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
* Native class that retrieves CPU stats from the kernel.
*/
public static class KernelCpuStatsReader {
+ protected boolean isSupportedFeature() {
+ return nativeIsSupportedFeature();
+ }
+
+ protected long readCpuStats(KernelCpuStatsCallback callback,
+ int[] scalingStepToPowerBracketMap, long lastUpdateTimestampNanos,
+ long[] outCpuTimeByScalingStep, long[] tempForUidStats) {
+ return nativeReadCpuStats(callback, scalingStepToPowerBracketMap,
+ lastUpdateTimestampNanos, outCpuTimeByScalingStep, tempForUidStats);
+ }
+
protected native boolean nativeIsSupportedFeature();
protected native long nativeReadCpuStats(KernelCpuStatsCallback callback,
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java
new file mode 100644
index 000000000000..1bcb2c4bc5fa
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java
@@ -0,0 +1,178 @@
+/*
+ * 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.power.stats;
+
+import android.os.PersistableBundle;
+
+/**
+ * Captures the positions and lengths of sections of the stats array, such as time-in-state,
+ * power usage estimates etc.
+ */
+public class CpuPowerStatsLayout extends PowerStatsLayout {
+ private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION = "dt";
+ private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT = "dtc";
+ private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION = "dc";
+ private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT = "dcc";
+ private static final String EXTRA_UID_BRACKETS_POSITION = "ub";
+ private static final String EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET = "us";
+
+ private int mDeviceCpuTimeByScalingStepPosition;
+ private int mDeviceCpuTimeByScalingStepCount;
+ private int mDeviceCpuTimeByClusterPosition;
+ private int mDeviceCpuTimeByClusterCount;
+
+ private int mUidPowerBracketsPosition;
+ private int mUidPowerBracketCount;
+
+ private int[] mScalingStepToPowerBracketMap;
+
+ /**
+ * Declare that the stats array has a section capturing CPU time per scaling step
+ */
+ public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) {
+ mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount);
+ mDeviceCpuTimeByScalingStepCount = scalingStepCount;
+ }
+
+ public int getCpuScalingStepCount() {
+ return mDeviceCpuTimeByScalingStepCount;
+ }
+
+ /**
+ * Saves the time duration in the <code>stats</code> element
+ * corresponding to the CPU scaling <code>state</code>.
+ */
+ public void setTimeByScalingStep(long[] stats, int step, long value) {
+ stats[mDeviceCpuTimeByScalingStepPosition + step] = value;
+ }
+
+ /**
+ * Extracts the time duration from the <code>stats</code> element
+ * corresponding to the CPU scaling <code>step</code>.
+ */
+ public long getTimeByScalingStep(long[] stats, int step) {
+ return stats[mDeviceCpuTimeByScalingStepPosition + step];
+ }
+
+ /**
+ * Declare that the stats array has a section capturing CPU time in each cluster
+ */
+ public void addDeviceSectionCpuTimeByCluster(int clusterCount) {
+ mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount);
+ mDeviceCpuTimeByClusterCount = clusterCount;
+ }
+
+ public int getCpuClusterCount() {
+ return mDeviceCpuTimeByClusterCount;
+ }
+
+ /**
+ * Saves the time duration in the <code>stats</code> element
+ * corresponding to the CPU <code>cluster</code>.
+ */
+ public void setTimeByCluster(long[] stats, int cluster, long value) {
+ stats[mDeviceCpuTimeByClusterPosition + cluster] = value;
+ }
+
+ /**
+ * Extracts the time duration from the <code>stats</code> element
+ * corresponding to the CPU <code>cluster</code>.
+ */
+ public long getTimeByCluster(long[] stats, int cluster) {
+ return stats[mDeviceCpuTimeByClusterPosition + cluster];
+ }
+
+ /**
+ * Declare that the UID stats array has a section capturing CPU time per power bracket.
+ */
+ public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) {
+ mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap;
+ updatePowerBracketCount();
+ mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount);
+ }
+
+ private void updatePowerBracketCount() {
+ mUidPowerBracketCount = 1;
+ for (int bracket : mScalingStepToPowerBracketMap) {
+ if (bracket >= mUidPowerBracketCount) {
+ mUidPowerBracketCount = bracket + 1;
+ }
+ }
+ }
+
+ public int[] getScalingStepToPowerBracketMap() {
+ return mScalingStepToPowerBracketMap;
+ }
+
+ public int getCpuPowerBracketCount() {
+ return mUidPowerBracketCount;
+ }
+
+ /**
+ * Saves time in <code>bracket</code> in the corresponding section of <code>stats</code>.
+ */
+ public void setUidTimeByPowerBracket(long[] stats, int bracket, long value) {
+ stats[mUidPowerBracketsPosition + bracket] = value;
+ }
+
+ /**
+ * Extracts the time in <code>bracket</code> from a UID stats array.
+ */
+ public long getUidTimeByPowerBracket(long[] stats, int bracket) {
+ return stats[mUidPowerBracketsPosition + bracket];
+ }
+
+ /**
+ * Copies the elements of the stats array layout into <code>extras</code>
+ */
+ public void toExtras(PersistableBundle extras) {
+ super.toExtras(extras);
+ extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION,
+ mDeviceCpuTimeByScalingStepPosition);
+ extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT,
+ mDeviceCpuTimeByScalingStepCount);
+ extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION,
+ mDeviceCpuTimeByClusterPosition);
+ extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT,
+ mDeviceCpuTimeByClusterCount);
+ extras.putInt(EXTRA_UID_BRACKETS_POSITION, mUidPowerBracketsPosition);
+ putIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET,
+ mScalingStepToPowerBracketMap);
+ }
+
+ /**
+ * Retrieves elements of the stats array layout from <code>extras</code>
+ */
+ public void fromExtras(PersistableBundle extras) {
+ super.fromExtras(extras);
+ mDeviceCpuTimeByScalingStepPosition =
+ extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION);
+ mDeviceCpuTimeByScalingStepCount =
+ extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT);
+ mDeviceCpuTimeByClusterPosition =
+ extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION);
+ mDeviceCpuTimeByClusterCount =
+ extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT);
+ mUidPowerBracketsPosition = extras.getInt(EXTRA_UID_BRACKETS_POSITION);
+ mScalingStepToPowerBracketMap =
+ getIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET);
+ if (mScalingStepToPowerBracketMap == null) {
+ mScalingStepToPowerBracketMap = new int[mDeviceCpuTimeByScalingStepCount];
+ }
+ updatePowerBracketCount();
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
index ed9414ff53a1..c34b8a8dc992 100644
--- a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
@@ -29,8 +29,8 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
-public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProcessor {
- private static final String TAG = "CpuAggregatedPowerStatsProcessor";
+public class CpuPowerStatsProcessor extends PowerStatsProcessor {
+ private static final String TAG = "CpuPowerStatsProcessor";
private static final double HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
private static final int UNKNOWN = -1;
@@ -64,7 +64,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces
private PowerStats.Descriptor mLastUsedDescriptor;
// Cached results of parsing of current PowerStats.Descriptor. Only refreshed when
// mLastUsedDescriptor changes
- private CpuPowerStatsCollector.CpuStatsArrayLayout mStatsLayout;
+ private CpuPowerStatsLayout mStatsLayout;
// Sequence of steps for power estimation and intermediate results.
private PowerEstimationPlan mPlan;
@@ -73,8 +73,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces
// Temp array for retrieval of UID power stats, to avoid repeated allocations
private long[] mTmpUidStatsArray;
- public CpuAggregatedPowerStatsProcessor(PowerProfile powerProfile,
- CpuScalingPolicies scalingPolicies) {
+ public CpuPowerStatsProcessor(PowerProfile powerProfile, CpuScalingPolicies scalingPolicies) {
mCpuScalingPolicies = scalingPolicies;
mCpuScalingStepCount = scalingPolicies.getScalingStepCount();
mScalingStepToCluster = new int[mCpuScalingStepCount];
@@ -106,7 +105,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces
}
mLastUsedDescriptor = descriptor;
- mStatsLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout();
+ mStatsLayout = new CpuPowerStatsLayout();
mStatsLayout.fromExtras(descriptor.extras);
mTmpDeviceStatsArray = new long[descriptor.statsArrayLength];
@@ -527,6 +526,12 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces
}
@Override
+ String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
+ // Unsupported for this power component
+ return null;
+ }
+
+ @Override
public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
unpackPowerStatsDescriptor(descriptor);
StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
index 9ea143e5c201..c01363a9c7ba 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
@@ -387,92 +387,14 @@ public class MobileRadioPowerCalculator extends PowerCalculator {
return consumptionMah;
}
- private static long buildModemPowerProfileKey(@ModemPowerProfile.ModemDrainType int drainType,
- @BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange,
- int txLevel) {
- long key = PowerProfile.SUBSYSTEM_MODEM;
-
- // Attach Modem drain type to the key if specified.
- if (drainType != IGNORE) {
- key |= drainType;
- }
-
- // Attach RadioAccessTechnology to the key if specified.
- switch (rat) {
- case IGNORE:
- // do nothing
- break;
- case BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER:
- key |= ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT;
- break;
- case BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE:
- key |= ModemPowerProfile.MODEM_RAT_TYPE_LTE;
- break;
- case BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR:
- key |= ModemPowerProfile.MODEM_RAT_TYPE_NR;
- break;
- default:
- Log.w(TAG, "Unexpected RadioAccessTechnology : " + rat);
- }
-
- // Attach NR Frequency Range to the key if specified.
- switch (freqRange) {
- case IGNORE:
- // do nothing
- break;
- case ServiceState.FREQUENCY_RANGE_UNKNOWN:
- key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT;
- break;
- case ServiceState.FREQUENCY_RANGE_LOW:
- key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW;
- break;
- case ServiceState.FREQUENCY_RANGE_MID:
- key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID;
- break;
- case ServiceState.FREQUENCY_RANGE_HIGH:
- key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH;
- break;
- case ServiceState.FREQUENCY_RANGE_MMWAVE:
- key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE;
- break;
- default:
- Log.w(TAG, "Unexpected NR frequency range : " + freqRange);
- }
-
- // Attach transmission level to the key if specified.
- switch (txLevel) {
- case IGNORE:
- // do nothing
- break;
- case 0:
- key |= ModemPowerProfile.MODEM_TX_LEVEL_0;
- break;
- case 1:
- key |= ModemPowerProfile.MODEM_TX_LEVEL_1;
- break;
- case 2:
- key |= ModemPowerProfile.MODEM_TX_LEVEL_2;
- break;
- case 3:
- key |= ModemPowerProfile.MODEM_TX_LEVEL_3;
- break;
- case 4:
- key |= ModemPowerProfile.MODEM_TX_LEVEL_4;
- break;
- default:
- Log.w(TAG, "Unexpected transmission level : " + txLevel);
- }
- return key;
- }
-
/**
* Calculates active receive radio power consumption (in milliamp-hours) from the given state's
* duration.
*/
public double calcRxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
@ServiceState.FrequencyRange int freqRange, long rxDurationMs) {
- final long rxKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat,
- freqRange, IGNORE);
+ final long rxKey = ModemPowerProfile.getAverageBatteryDrainKey(
+ ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat, freqRange, IGNORE);
final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(rxKey,
Double.NaN);
if (Double.isNaN(drainRateMa)) {
@@ -495,8 +417,8 @@ public class MobileRadioPowerCalculator extends PowerCalculator {
*/
public double calcTxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
@ServiceState.FrequencyRange int freqRange, int txLevel, long txDurationMs) {
- final long txKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_TX, rat,
- freqRange, txLevel);
+ final long txKey = ModemPowerProfile.getAverageBatteryDrainKey(
+ ModemPowerProfile.MODEM_DRAIN_TYPE_TX, rat, freqRange, txLevel);
final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(txKey,
Double.NaN);
if (Double.isNaN(drainRateMa)) {
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
new file mode 100644
index 000000000000..8c154e4a0875
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
@@ -0,0 +1,392 @@
+/*
+ * 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.power.stats;
+
+import android.content.pm.PackageManager;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.net.NetworkStats;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.OutcomeReceiver;
+import android.os.PersistableBundle;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.ModemActivityInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerStats;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.IntSupplier;
+import java.util.function.LongSupplier;
+import java.util.function.Supplier;
+
+public class MobileRadioPowerStatsCollector extends PowerStatsCollector {
+ private static final String TAG = "MobileRadioPowerStatsCollector";
+
+ /**
+ * The soonest the Mobile Radio stats can be updated due to a mobile radio power state change
+ * after it was last updated.
+ */
+ @VisibleForTesting
+ protected static final long MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS = 1000 * 60 * 10;
+
+ private static final long MODEM_ACTIVITY_REQUEST_TIMEOUT = 20000;
+
+ private static final long ENERGY_UNSPECIFIED = -1;
+
+ @VisibleForTesting
+ @AccessNetworkConstants.RadioAccessNetworkType
+ static final int[] NETWORK_TYPES = {
+ AccessNetworkConstants.AccessNetworkType.UNKNOWN,
+ AccessNetworkConstants.AccessNetworkType.GERAN,
+ AccessNetworkConstants.AccessNetworkType.UTRAN,
+ AccessNetworkConstants.AccessNetworkType.EUTRAN,
+ AccessNetworkConstants.AccessNetworkType.CDMA2000,
+ AccessNetworkConstants.AccessNetworkType.IWLAN,
+ AccessNetworkConstants.AccessNetworkType.NGRAN
+ };
+
+ interface Injector {
+ Handler getHandler();
+ Clock getClock();
+ PowerStatsUidResolver getUidResolver();
+ PackageManager getPackageManager();
+ ConsumedEnergyRetriever getConsumedEnergyRetriever();
+ IntSupplier getVoltageSupplier();
+ Supplier<NetworkStats> getMobileNetworkStatsSupplier();
+ TelephonyManager getTelephonyManager();
+ LongSupplier getCallDurationSupplier();
+ LongSupplier getPhoneSignalScanDurationSupplier();
+ }
+
+ private final Injector mInjector;
+
+ private MobileRadioPowerStatsLayout mLayout;
+ private boolean mIsInitialized;
+
+ private PowerStats mPowerStats;
+ private long[] mDeviceStats;
+ private PowerStatsUidResolver mPowerStatsUidResolver;
+ private volatile TelephonyManager mTelephonyManager;
+ private LongSupplier mCallDurationSupplier;
+ private LongSupplier mScanDurationSupplier;
+ private volatile Supplier<NetworkStats> mNetworkStatsSupplier;
+ private ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ private IntSupplier mVoltageSupplier;
+ private int[] mEnergyConsumerIds = new int[0];
+ private long mLastUpdateTimestampMillis;
+ private ModemActivityInfo mLastModemActivityInfo;
+ private NetworkStats mLastNetworkStats;
+ private long[] mLastConsumedEnergyUws;
+ private int mLastVoltageMv;
+ private long mLastCallDuration;
+ private long mLastScanDuration;
+
+ public MobileRadioPowerStatsCollector(Injector injector, long throttlePeriodMs) {
+ super(injector.getHandler(), throttlePeriodMs, injector.getClock());
+ mInjector = injector;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ if (enabled) {
+ PackageManager packageManager = mInjector.getPackageManager();
+ super.setEnabled(packageManager != null
+ && packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+ } else {
+ super.setEnabled(false);
+ }
+ }
+
+ private boolean ensureInitialized() {
+ if (mIsInitialized) {
+ return true;
+ }
+
+ if (!isEnabled()) {
+ return false;
+ }
+
+ mPowerStatsUidResolver = mInjector.getUidResolver();
+ mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
+ mVoltageSupplier = mInjector.getVoltageSupplier();
+
+ mTelephonyManager = mInjector.getTelephonyManager();
+ mNetworkStatsSupplier = mInjector.getMobileNetworkStatsSupplier();
+ mCallDurationSupplier = mInjector.getCallDurationSupplier();
+ mScanDurationSupplier = mInjector.getPhoneSignalScanDurationSupplier();
+
+ mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(
+ EnergyConsumerType.MOBILE_RADIO);
+ mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length];
+ Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
+
+ mLayout = new MobileRadioPowerStatsLayout();
+ mLayout.addDeviceMobileActivity();
+ mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length);
+ mLayout.addStateStats();
+ mLayout.addUidNetworkStats();
+ mLayout.addDeviceSectionUsageDuration();
+ mLayout.addDeviceSectionPowerEstimate();
+ mLayout.addUidSectionPowerEstimate();
+
+ SparseArray<String> stateLabels = new SparseArray<>();
+ for (int rat = 0; rat < BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
+ final int freqCount = rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR
+ ? ServiceState.FREQUENCY_RANGE_COUNT : 1;
+ for (int freq = 0; freq < freqCount; freq++) {
+ int stateKey = makeStateKey(rat, freq);
+ StringBuilder sb = new StringBuilder();
+ if (rat != BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER) {
+ sb.append(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+ }
+ if (freq != ServiceState.FREQUENCY_RANGE_UNKNOWN) {
+ if (!sb.isEmpty()) {
+ sb.append(" ");
+ }
+ sb.append(ServiceState.frequencyRangeToString(freq));
+ }
+ stateLabels.put(stateKey, !sb.isEmpty() ? sb.toString() : "other");
+ }
+ }
+
+ PersistableBundle extras = new PersistableBundle();
+ mLayout.toExtras(extras);
+ PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, mLayout.getDeviceStatsArrayLength(),
+ stateLabels, mLayout.getStateStatsArrayLength(), mLayout.getUidStatsArrayLength(),
+ extras);
+ mPowerStats = new PowerStats(powerStatsDescriptor);
+ mDeviceStats = mPowerStats.stats;
+
+ mIsInitialized = true;
+ return true;
+ }
+
+ @Override
+ protected PowerStats collectStats() {
+ if (!ensureInitialized()) {
+ return null;
+ }
+
+ collectModemActivityInfo();
+
+ collectNetworkStats();
+
+ if (mEnergyConsumerIds.length != 0) {
+ collectEnergyConsumers();
+ }
+
+ if (mPowerStats.durationMs == 0) {
+ setTimestamp(mClock.elapsedRealtime());
+ }
+
+ return mPowerStats;
+ }
+
+ private void collectModemActivityInfo() {
+ if (mTelephonyManager == null) {
+ return;
+ }
+
+ CompletableFuture<ModemActivityInfo> immediateFuture = new CompletableFuture<>();
+ mTelephonyManager.requestModemActivityInfo(Runnable::run,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(ModemActivityInfo result) {
+ immediateFuture.complete(result);
+ }
+
+ @Override
+ public void onError(TelephonyManager.ModemActivityInfoException e) {
+ Slog.w(TAG, "error reading modem stats:" + e);
+ immediateFuture.complete(null);
+ }
+ });
+
+ ModemActivityInfo activityInfo;
+ try {
+ activityInfo = immediateFuture.get(MODEM_ACTIVITY_REQUEST_TIMEOUT,
+ TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ Slog.e(TAG, "Cannot acquire ModemActivityInfo");
+ activityInfo = null;
+ }
+
+ ModemActivityInfo deltaInfo = mLastModemActivityInfo == null
+ ? (activityInfo == null ? null : activityInfo.getDelta(activityInfo))
+ : mLastModemActivityInfo.getDelta(activityInfo);
+
+ mLastModemActivityInfo = activityInfo;
+
+ if (deltaInfo == null) {
+ return;
+ }
+
+ setTimestamp(deltaInfo.getTimestampMillis());
+ mLayout.setDeviceSleepTime(mDeviceStats, deltaInfo.getSleepTimeMillis());
+ mLayout.setDeviceIdleTime(mDeviceStats, deltaInfo.getIdleTimeMillis());
+
+ long callDuration = mCallDurationSupplier.getAsLong();
+ if (callDuration >= mLastCallDuration) {
+ mLayout.setDeviceCallTime(mDeviceStats, callDuration - mLastCallDuration);
+ }
+ mLastCallDuration = callDuration;
+
+ long scanDuration = mScanDurationSupplier.getAsLong();
+ if (scanDuration >= mLastScanDuration) {
+ mLayout.setDeviceScanTime(mDeviceStats, scanDuration - mLastScanDuration);
+ }
+ mLastScanDuration = scanDuration;
+
+ SparseArray<long[]> stateStats = mPowerStats.stateStats;
+ stateStats.clear();
+
+ if (deltaInfo.getSpecificInfoLength() == 0) {
+ mLayout.addRxTxTimesForRat(stateStats,
+ AccessNetworkConstants.AccessNetworkType.UNKNOWN,
+ ServiceState.FREQUENCY_RANGE_UNKNOWN,
+ deltaInfo.getReceiveTimeMillis(),
+ deltaInfo.getTransmitTimeMillis());
+ } else {
+ for (int rat = 0; rat < NETWORK_TYPES.length; rat++) {
+ if (rat == AccessNetworkConstants.AccessNetworkType.NGRAN) {
+ for (int freq = 0; freq < ServiceState.FREQUENCY_RANGE_COUNT; freq++) {
+ mLayout.addRxTxTimesForRat(stateStats, rat, freq,
+ deltaInfo.getReceiveTimeMillis(rat, freq),
+ deltaInfo.getTransmitTimeMillis(rat, freq));
+ }
+ } else {
+ mLayout.addRxTxTimesForRat(stateStats, rat,
+ ServiceState.FREQUENCY_RANGE_UNKNOWN,
+ deltaInfo.getReceiveTimeMillis(rat),
+ deltaInfo.getTransmitTimeMillis(rat));
+ }
+ }
+ }
+ }
+
+ private void collectNetworkStats() {
+ mPowerStats.uidStats.clear();
+
+ NetworkStats networkStats = mNetworkStatsSupplier.get();
+ if (networkStats == null) {
+ return;
+ }
+
+ List<BatteryStatsImpl.NetworkStatsDelta> delta =
+ BatteryStatsImpl.computeDelta(networkStats, mLastNetworkStats);
+ mLastNetworkStats = networkStats;
+ for (int i = delta.size() - 1; i >= 0; i--) {
+ BatteryStatsImpl.NetworkStatsDelta uidDelta = delta.get(i);
+ long rxBytes = uidDelta.getRxBytes();
+ long txBytes = uidDelta.getTxBytes();
+ long rxPackets = uidDelta.getRxPackets();
+ long txPackets = uidDelta.getTxPackets();
+ if (rxBytes == 0 && txBytes == 0 && rxPackets == 0 && txPackets == 0) {
+ continue;
+ }
+
+ int uid = mPowerStatsUidResolver.mapUid(uidDelta.getUid());
+ long[] stats = mPowerStats.uidStats.get(uid);
+ if (stats == null) {
+ stats = new long[mLayout.getUidStatsArrayLength()];
+ mPowerStats.uidStats.put(uid, stats);
+ mLayout.setUidRxBytes(stats, rxBytes);
+ mLayout.setUidTxBytes(stats, txBytes);
+ mLayout.setUidRxPackets(stats, rxPackets);
+ mLayout.setUidTxPackets(stats, txPackets);
+ } else {
+ mLayout.setUidRxBytes(stats, mLayout.getUidRxBytes(stats) + rxBytes);
+ mLayout.setUidTxBytes(stats, mLayout.getUidTxBytes(stats) + txBytes);
+ mLayout.setUidRxPackets(stats, mLayout.getUidRxPackets(stats) + rxPackets);
+ mLayout.setUidTxPackets(stats, mLayout.getUidTxPackets(stats) + txPackets);
+ }
+ }
+ }
+
+ private void collectEnergyConsumers() {
+ int voltageMv = mVoltageSupplier.getAsInt();
+ if (voltageMv <= 0) {
+ Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
+ + " mV) when querying energy consumers");
+ return;
+ }
+
+ int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
+ mLastVoltageMv = voltageMv;
+
+ long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds);
+ if (energyUws == null) {
+ return;
+ }
+
+ for (int i = energyUws.length - 1; i >= 0; i--) {
+ long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED
+ ? energyUws[i] - mLastConsumedEnergyUws[i] : 0;
+ if (energyDelta < 0) {
+ // Likely, restart of powerstats HAL
+ energyDelta = 0;
+ }
+ mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage));
+ mLastConsumedEnergyUws[i] = energyUws[i];
+ }
+ }
+
+ static int makeStateKey(int rat, int freqRange) {
+ if (rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR) {
+ return rat | (freqRange << 8);
+ } else {
+ return rat;
+ }
+ }
+
+ private void setTimestamp(long timestamp) {
+ mPowerStats.durationMs = Math.max(timestamp - mLastUpdateTimestampMillis, 0);
+ mLastUpdateTimestampMillis = timestamp;
+ }
+
+ @BatteryStats.RadioAccessTechnology
+ static int mapRadioAccessNetworkTypeToRadioAccessTechnology(
+ @AccessNetworkConstants.RadioAccessNetworkType int networkType) {
+ switch (networkType) {
+ case AccessNetworkConstants.AccessNetworkType.NGRAN:
+ return BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR;
+ case AccessNetworkConstants.AccessNetworkType.EUTRAN:
+ return BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE;
+ case AccessNetworkConstants.AccessNetworkType.UNKNOWN: //fallthrough
+ case AccessNetworkConstants.AccessNetworkType.GERAN: //fallthrough
+ case AccessNetworkConstants.AccessNetworkType.UTRAN: //fallthrough
+ case AccessNetworkConstants.AccessNetworkType.CDMA2000: //fallthrough
+ case AccessNetworkConstants.AccessNetworkType.IWLAN:
+ return BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER;
+ default:
+ Slog.w(TAG,
+ "Unhandled RadioAccessNetworkType (" + networkType + "), mapping to OTHER");
+ return BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java
new file mode 100644
index 000000000000..81d7c2fa2880
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.annotation.NonNull;
+import android.os.PersistableBundle;
+import android.telephony.ModemActivityInfo;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.os.PowerStats;
+
+/**
+ * Captures the positions and lengths of sections of the stats array, such as time-in-state,
+ * power usage estimates etc.
+ */
+class MobileRadioPowerStatsLayout extends PowerStatsLayout {
+ private static final String TAG = "MobileRadioPowerStatsLayout";
+ private static final String EXTRA_DEVICE_SLEEP_TIME_POSITION = "dt-sleep";
+ private static final String EXTRA_DEVICE_IDLE_TIME_POSITION = "dt-idle";
+ private static final String EXTRA_DEVICE_SCAN_TIME_POSITION = "dt-scan";
+ private static final String EXTRA_DEVICE_CALL_TIME_POSITION = "dt-call";
+ private static final String EXTRA_DEVICE_CALL_POWER_POSITION = "dp-call";
+ private static final String EXTRA_STATE_RX_TIME_POSITION = "srx";
+ private static final String EXTRA_STATE_TX_TIMES_POSITION = "stx";
+ private static final String EXTRA_STATE_TX_TIMES_COUNT = "stxc";
+ private static final String EXTRA_UID_RX_BYTES_POSITION = "urxb";
+ private static final String EXTRA_UID_TX_BYTES_POSITION = "utxb";
+ private static final String EXTRA_UID_RX_PACKETS_POSITION = "urxp";
+ private static final String EXTRA_UID_TX_PACKETS_POSITION = "utxp";
+
+ private int mDeviceSleepTimePosition;
+ private int mDeviceIdleTimePosition;
+ private int mDeviceScanTimePosition;
+ private int mDeviceCallTimePosition;
+ private int mDeviceCallPowerPosition;
+ private int mStateRxTimePosition;
+ private int mStateTxTimesPosition;
+ private int mStateTxTimesCount;
+ private int mUidRxBytesPosition;
+ private int mUidTxBytesPosition;
+ private int mUidRxPacketsPosition;
+ private int mUidTxPacketsPosition;
+
+ MobileRadioPowerStatsLayout() {
+ }
+
+ MobileRadioPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) {
+ super(descriptor);
+ }
+
+ void addDeviceMobileActivity() {
+ mDeviceSleepTimePosition = addDeviceSection(1);
+ mDeviceIdleTimePosition = addDeviceSection(1);
+ mDeviceScanTimePosition = addDeviceSection(1);
+ mDeviceCallTimePosition = addDeviceSection(1);
+ }
+
+ void addStateStats() {
+ mStateRxTimePosition = addStateSection(1);
+ mStateTxTimesCount = ModemActivityInfo.getNumTxPowerLevels();
+ mStateTxTimesPosition = addStateSection(mStateTxTimesCount);
+ }
+
+ void addUidNetworkStats() {
+ mUidRxBytesPosition = addUidSection(1);
+ mUidTxBytesPosition = addUidSection(1);
+ mUidRxPacketsPosition = addUidSection(1);
+ mUidTxPacketsPosition = addUidSection(1);
+ }
+
+ @Override
+ public void addDeviceSectionPowerEstimate() {
+ super.addDeviceSectionPowerEstimate();
+ mDeviceCallPowerPosition = addDeviceSection(1);
+ }
+
+ public void setDeviceSleepTime(long[] stats, long durationMillis) {
+ stats[mDeviceSleepTimePosition] = durationMillis;
+ }
+
+ public long getDeviceSleepTime(long[] stats) {
+ return stats[mDeviceSleepTimePosition];
+ }
+
+ public void setDeviceIdleTime(long[] stats, long durationMillis) {
+ stats[mDeviceIdleTimePosition] = durationMillis;
+ }
+
+ public long getDeviceIdleTime(long[] stats) {
+ return stats[mDeviceIdleTimePosition];
+ }
+
+ public void setDeviceScanTime(long[] stats, long durationMillis) {
+ stats[mDeviceScanTimePosition] = durationMillis;
+ }
+
+ public long getDeviceScanTime(long[] stats) {
+ return stats[mDeviceScanTimePosition];
+ }
+
+ public void setDeviceCallTime(long[] stats, long durationMillis) {
+ stats[mDeviceCallTimePosition] = durationMillis;
+ }
+
+ public long getDeviceCallTime(long[] stats) {
+ return stats[mDeviceCallTimePosition];
+ }
+
+ public void setDeviceCallPowerEstimate(long[] stats, double power) {
+ stats[mDeviceCallPowerPosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
+ }
+
+ public double getDeviceCallPowerEstimate(long[] stats) {
+ return stats[mDeviceCallPowerPosition] / MILLI_TO_NANO_MULTIPLIER;
+ }
+
+ public void setStateRxTime(long[] stats, long durationMillis) {
+ stats[mStateRxTimePosition] = durationMillis;
+ }
+
+ public long getStateRxTime(long[] stats) {
+ return stats[mStateRxTimePosition];
+ }
+
+ public void setStateTxTime(long[] stats, int level, int durationMillis) {
+ stats[mStateTxTimesPosition + level] = durationMillis;
+ }
+
+ public long getStateTxTime(long[] stats, int level) {
+ return stats[mStateTxTimesPosition + level];
+ }
+
+ public void setUidRxBytes(long[] stats, long count) {
+ stats[mUidRxBytesPosition] = count;
+ }
+
+ public long getUidRxBytes(long[] stats) {
+ return stats[mUidRxBytesPosition];
+ }
+
+ public void setUidTxBytes(long[] stats, long count) {
+ stats[mUidTxBytesPosition] = count;
+ }
+
+ public long getUidTxBytes(long[] stats) {
+ return stats[mUidTxBytesPosition];
+ }
+
+ public void setUidRxPackets(long[] stats, long count) {
+ stats[mUidRxPacketsPosition] = count;
+ }
+
+ public long getUidRxPackets(long[] stats) {
+ return stats[mUidRxPacketsPosition];
+ }
+
+ public void setUidTxPackets(long[] stats, long count) {
+ stats[mUidTxPacketsPosition] = count;
+ }
+
+ public long getUidTxPackets(long[] stats) {
+ return stats[mUidTxPacketsPosition];
+ }
+
+ /**
+ * Copies the elements of the stats array layout into <code>extras</code>
+ */
+ public void toExtras(PersistableBundle extras) {
+ super.toExtras(extras);
+ extras.putInt(EXTRA_DEVICE_SLEEP_TIME_POSITION, mDeviceSleepTimePosition);
+ extras.putInt(EXTRA_DEVICE_IDLE_TIME_POSITION, mDeviceIdleTimePosition);
+ extras.putInt(EXTRA_DEVICE_SCAN_TIME_POSITION, mDeviceScanTimePosition);
+ extras.putInt(EXTRA_DEVICE_CALL_TIME_POSITION, mDeviceCallTimePosition);
+ extras.putInt(EXTRA_DEVICE_CALL_POWER_POSITION, mDeviceCallPowerPosition);
+ extras.putInt(EXTRA_STATE_RX_TIME_POSITION, mStateRxTimePosition);
+ extras.putInt(EXTRA_STATE_TX_TIMES_POSITION, mStateTxTimesPosition);
+ extras.putInt(EXTRA_STATE_TX_TIMES_COUNT, mStateTxTimesCount);
+ extras.putInt(EXTRA_UID_RX_BYTES_POSITION, mUidRxBytesPosition);
+ extras.putInt(EXTRA_UID_TX_BYTES_POSITION, mUidTxBytesPosition);
+ extras.putInt(EXTRA_UID_RX_PACKETS_POSITION, mUidRxPacketsPosition);
+ extras.putInt(EXTRA_UID_TX_PACKETS_POSITION, mUidTxPacketsPosition);
+ }
+
+ /**
+ * Retrieves elements of the stats array layout from <code>extras</code>
+ */
+ public void fromExtras(PersistableBundle extras) {
+ super.fromExtras(extras);
+ mDeviceSleepTimePosition = extras.getInt(EXTRA_DEVICE_SLEEP_TIME_POSITION);
+ mDeviceIdleTimePosition = extras.getInt(EXTRA_DEVICE_IDLE_TIME_POSITION);
+ mDeviceScanTimePosition = extras.getInt(EXTRA_DEVICE_SCAN_TIME_POSITION);
+ mDeviceCallTimePosition = extras.getInt(EXTRA_DEVICE_CALL_TIME_POSITION);
+ mDeviceCallPowerPosition = extras.getInt(EXTRA_DEVICE_CALL_POWER_POSITION);
+ mStateRxTimePosition = extras.getInt(EXTRA_STATE_RX_TIME_POSITION);
+ mStateTxTimesPosition = extras.getInt(EXTRA_STATE_TX_TIMES_POSITION);
+ mStateTxTimesCount = extras.getInt(EXTRA_STATE_TX_TIMES_COUNT);
+ mUidRxBytesPosition = extras.getInt(EXTRA_UID_RX_BYTES_POSITION);
+ mUidTxBytesPosition = extras.getInt(EXTRA_UID_TX_BYTES_POSITION);
+ mUidRxPacketsPosition = extras.getInt(EXTRA_UID_RX_PACKETS_POSITION);
+ mUidTxPacketsPosition = extras.getInt(EXTRA_UID_TX_PACKETS_POSITION);
+ }
+
+ public void addRxTxTimesForRat(SparseArray<long[]> stateStats, int networkType, int freqRange,
+ long rxTime, int[] txTime) {
+ if (txTime.length != mStateTxTimesCount) {
+ Slog.wtf(TAG, "Invalid TX time array size: " + txTime.length);
+ return;
+ }
+
+ boolean nonZero = false;
+ if (rxTime != 0) {
+ nonZero = true;
+ } else {
+ for (int i = txTime.length - 1; i >= 0; i--) {
+ if (txTime[i] != 0) {
+ nonZero = true;
+ break;
+ }
+ }
+ }
+
+ if (!nonZero) {
+ return;
+ }
+
+ int rat = MobileRadioPowerStatsCollector.mapRadioAccessNetworkTypeToRadioAccessTechnology(
+ networkType);
+ int stateKey = MobileRadioPowerStatsCollector.makeStateKey(rat, freqRange);
+ long[] stats = stateStats.get(stateKey);
+ if (stats == null) {
+ stats = new long[getStateStatsArrayLength()];
+ stateStats.put(stateKey, stats);
+ }
+
+ stats[mStateRxTimePosition] += rxTime;
+ for (int i = mStateTxTimesCount - 1; i >= 0; i--) {
+ stats[mStateTxTimesPosition + i] += txTime[i];
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
new file mode 100644
index 000000000000..c97c64bafcba
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
@@ -0,0 +1,434 @@
+/*
+ * 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.power.stats;
+
+import android.os.BatteryStats;
+import android.telephony.CellSignalStrength;
+import android.telephony.ModemActivityInfo;
+import android.telephony.ServiceState;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+import com.android.internal.power.ModemPowerProfile;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class MobileRadioPowerStatsProcessor extends PowerStatsProcessor {
+ private static final String TAG = "MobileRadioPowerStatsProcessor";
+ private static final boolean DEBUG = false;
+
+ private static final int NUM_SIGNAL_STRENGTH_LEVELS =
+ CellSignalStrength.getNumSignalStrengthLevels();
+ private static final int IGNORE = -1;
+
+ private final UsageBasedPowerEstimator mSleepPowerEstimator;
+ private final UsageBasedPowerEstimator mIdlePowerEstimator;
+ private final UsageBasedPowerEstimator mCallPowerEstimator;
+ private final UsageBasedPowerEstimator mScanPowerEstimator;
+
+ private static class RxTxPowerEstimators {
+ UsageBasedPowerEstimator mRxPowerEstimator;
+ UsageBasedPowerEstimator[] mTxPowerEstimators =
+ new UsageBasedPowerEstimator[ModemActivityInfo.getNumTxPowerLevels()];
+ }
+
+ private final SparseArray<RxTxPowerEstimators> mRxTxPowerEstimators = new SparseArray<>();
+
+ private PowerStats.Descriptor mLastUsedDescriptor;
+ private MobileRadioPowerStatsLayout mStatsLayout;
+ // Sequence of steps for power estimation and intermediate results.
+ private PowerEstimationPlan mPlan;
+
+ private long[] mTmpDeviceStatsArray;
+ private long[] mTmpStateStatsArray;
+ private long[] mTmpUidStatsArray;
+
+ public MobileRadioPowerStatsProcessor(PowerProfile powerProfile) {
+ final double sleepDrainRateMa = powerProfile.getAverageBatteryDrainOrDefaultMa(
+ PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP,
+ Double.NaN);
+ if (Double.isNaN(sleepDrainRateMa)) {
+ mSleepPowerEstimator = null;
+ } else {
+ mSleepPowerEstimator = new UsageBasedPowerEstimator(sleepDrainRateMa);
+ }
+
+ final double idleDrainRateMa = powerProfile.getAverageBatteryDrainOrDefaultMa(
+ PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE,
+ Double.NaN);
+ if (Double.isNaN(idleDrainRateMa)) {
+ mIdlePowerEstimator = null;
+ } else {
+ mIdlePowerEstimator = new UsageBasedPowerEstimator(idleDrainRateMa);
+ }
+
+ // Instantiate legacy power estimators
+ double powerRadioActiveMa =
+ powerProfile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, Double.NaN);
+ if (Double.isNaN(powerRadioActiveMa)) {
+ double sum = 0;
+ sum += powerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
+ for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
+ sum += powerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
+ }
+ powerRadioActiveMa = sum / (NUM_SIGNAL_STRENGTH_LEVELS + 1);
+ }
+ mCallPowerEstimator = new UsageBasedPowerEstimator(powerRadioActiveMa);
+
+ mScanPowerEstimator = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_SCANNING, 0));
+
+ for (int rat = 0; rat < BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
+ final int freqCount = rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR
+ ? ServiceState.FREQUENCY_RANGE_COUNT : 1;
+ for (int freqRange = 0; freqRange < freqCount; freqRange++) {
+ mRxTxPowerEstimators.put(
+ MobileRadioPowerStatsCollector.makeStateKey(rat, freqRange),
+ buildRxTxPowerEstimators(powerProfile, rat, freqRange));
+ }
+ }
+ }
+
+ private static RxTxPowerEstimators buildRxTxPowerEstimators(PowerProfile powerProfile, int rat,
+ int freqRange) {
+ RxTxPowerEstimators estimators = new RxTxPowerEstimators();
+ long rxKey = ModemPowerProfile.getAverageBatteryDrainKey(
+ ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat, freqRange, IGNORE);
+ double rxDrainRateMa = powerProfile.getAverageBatteryDrainOrDefaultMa(rxKey, Double.NaN);
+ if (Double.isNaN(rxDrainRateMa)) {
+ Log.w(TAG, "Unavailable Power Profile constant for key 0x"
+ + Long.toHexString(rxKey));
+ rxDrainRateMa = 0;
+ }
+ estimators.mRxPowerEstimator = new UsageBasedPowerEstimator(rxDrainRateMa);
+ for (int txLevel = 0; txLevel < ModemActivityInfo.getNumTxPowerLevels(); txLevel++) {
+ long txKey = ModemPowerProfile.getAverageBatteryDrainKey(
+ ModemPowerProfile.MODEM_DRAIN_TYPE_TX, rat, freqRange, txLevel);
+ double txDrainRateMa = powerProfile.getAverageBatteryDrainOrDefaultMa(txKey,
+ Double.NaN);
+ if (Double.isNaN(txDrainRateMa)) {
+ Log.w(TAG, "Unavailable Power Profile constant for key 0x"
+ + Long.toHexString(txKey));
+ txDrainRateMa = 0;
+ }
+ estimators.mTxPowerEstimators[txLevel] = new UsageBasedPowerEstimator(txDrainRateMa);
+ }
+ return estimators;
+ }
+
+ private static class Intermediates {
+ /**
+ * Number of received packets
+ */
+ public long rxPackets;
+ /**
+ * Number of transmitted packets
+ */
+ public long txPackets;
+ /**
+ * Estimated power for the RX state of the modem.
+ */
+ public double rxPower;
+ /**
+ * Estimated power for the TX state of the modem.
+ */
+ public double txPower;
+ /**
+ * Estimated power for IDLE, SLEEP and CELL-SCAN states of the modem.
+ */
+ public double inactivePower;
+ /**
+ * Estimated power for IDLE, SLEEP and CELL-SCAN states of the modem.
+ */
+ public double callPower;
+ /**
+ * Measured consumed energy from power monitoring hardware (micro-coulombs)
+ */
+ public long consumedEnergy;
+ }
+
+ @Override
+ void finish(PowerComponentAggregatedPowerStats stats) {
+ if (stats.getPowerStatsDescriptor() == null) {
+ return;
+ }
+
+ unpackPowerStatsDescriptor(stats.getPowerStatsDescriptor());
+
+ if (mPlan == null) {
+ mPlan = new PowerEstimationPlan(stats.getConfig());
+ }
+
+ for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) {
+ DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i);
+ Intermediates intermediates = new Intermediates();
+ estimation.intermediates = intermediates;
+ computeDevicePowerEstimates(stats, estimation.stateValues, intermediates);
+ }
+
+ if (mStatsLayout.getEnergyConsumerCount() != 0) {
+ double ratio = computeEstimateAdjustmentRatioUsingConsumedEnergy();
+ if (ratio != 1) {
+ for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) {
+ DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i);
+ adjustDevicePowerEstimates(stats, estimation.stateValues,
+ (Intermediates) estimation.intermediates, ratio);
+ }
+ }
+ }
+
+ combineDeviceStateEstimates();
+
+ ArrayList<Integer> uids = new ArrayList<>();
+ stats.collectUids(uids);
+ if (!uids.isEmpty()) {
+ for (int uid : uids) {
+ for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) {
+ computeUidRxTxTotals(stats, uid, mPlan.uidStateEstimates.get(i));
+ }
+ }
+
+ for (int uid : uids) {
+ for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) {
+ computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(i));
+ }
+ }
+ }
+ mPlan.resetIntermediates();
+ }
+
+ private void unpackPowerStatsDescriptor(PowerStats.Descriptor descriptor) {
+ if (descriptor.equals(mLastUsedDescriptor)) {
+ return;
+ }
+
+ mLastUsedDescriptor = descriptor;
+ mStatsLayout = new MobileRadioPowerStatsLayout(descriptor);
+ mTmpDeviceStatsArray = new long[descriptor.statsArrayLength];
+ mTmpStateStatsArray = new long[descriptor.stateStatsArrayLength];
+ mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength];
+ }
+
+ /**
+ * Compute power estimates using the power profile.
+ */
+ private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats,
+ int[] deviceStates, Intermediates intermediates) {
+ if (!stats.getDeviceStats(mTmpDeviceStatsArray, deviceStates)) {
+ return;
+ }
+
+ for (int i = mStatsLayout.getEnergyConsumerCount() - 1; i >= 0; i--) {
+ intermediates.consumedEnergy += mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, i);
+ }
+
+ if (mSleepPowerEstimator != null) {
+ intermediates.inactivePower += mSleepPowerEstimator.calculatePower(
+ mStatsLayout.getDeviceSleepTime(mTmpDeviceStatsArray));
+ }
+
+ if (mIdlePowerEstimator != null) {
+ intermediates.inactivePower += mIdlePowerEstimator.calculatePower(
+ mStatsLayout.getDeviceIdleTime(mTmpDeviceStatsArray));
+ }
+
+ if (mScanPowerEstimator != null) {
+ intermediates.inactivePower += mScanPowerEstimator.calculatePower(
+ mStatsLayout.getDeviceScanTime(mTmpDeviceStatsArray));
+ }
+
+ stats.forEachStateStatsKey(key -> {
+ RxTxPowerEstimators estimators = mRxTxPowerEstimators.get(key);
+ stats.getStateStats(mTmpStateStatsArray, key, deviceStates);
+ long rxTime = mStatsLayout.getStateRxTime(mTmpStateStatsArray);
+ intermediates.rxPower += estimators.mRxPowerEstimator.calculatePower(rxTime);
+ for (int txLevel = 0; txLevel < ModemActivityInfo.getNumTxPowerLevels(); txLevel++) {
+ long txTime = mStatsLayout.getStateTxTime(mTmpStateStatsArray, txLevel);
+ intermediates.txPower +=
+ estimators.mTxPowerEstimators[txLevel].calculatePower(txTime);
+ }
+ });
+
+ if (mCallPowerEstimator != null) {
+ intermediates.callPower = mCallPowerEstimator.calculatePower(
+ mStatsLayout.getDeviceCallTime(mTmpDeviceStatsArray));
+ }
+
+ mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray,
+ intermediates.rxPower + intermediates.txPower + intermediates.inactivePower);
+ mStatsLayout.setDeviceCallPowerEstimate(mTmpDeviceStatsArray, intermediates.callPower);
+ stats.setDeviceStats(deviceStates, mTmpDeviceStatsArray);
+ }
+
+ /**
+ * Compute an adjustment ratio using the total power estimated using the power profile
+ * and the total power measured by hardware.
+ */
+ private double computeEstimateAdjustmentRatioUsingConsumedEnergy() {
+ long totalConsumedEnergy = 0;
+ double totalPower = 0;
+
+ for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) {
+ Intermediates intermediates =
+ (Intermediates) mPlan.deviceStateEstimations.get(i).intermediates;
+ totalPower += intermediates.rxPower + intermediates.txPower
+ + intermediates.inactivePower + intermediates.callPower;
+ totalConsumedEnergy += intermediates.consumedEnergy;
+ }
+
+ if (totalPower == 0) {
+ return 1;
+ }
+
+ return uCtoMah(totalConsumedEnergy) / totalPower;
+ }
+
+ /**
+ * Uniformly apply the same adjustment to all power estimates in order to ensure that the total
+ * estimated power matches the measured consumed power. We are not claiming that all
+ * averages captured in the power profile have to be off by the same percentage in reality.
+ */
+ private void adjustDevicePowerEstimates(PowerComponentAggregatedPowerStats stats,
+ int[] deviceStates, Intermediates intermediates, double ratio) {
+ intermediates.rxPower *= ratio;
+ intermediates.txPower *= ratio;
+ intermediates.inactivePower *= ratio;
+ intermediates.callPower *= ratio;
+
+ if (!stats.getDeviceStats(mTmpDeviceStatsArray, deviceStates)) {
+ return;
+ }
+
+ mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray,
+ intermediates.rxPower + intermediates.txPower + intermediates.inactivePower);
+ mStatsLayout.setDeviceCallPowerEstimate(mTmpDeviceStatsArray, intermediates.callPower);
+ stats.setDeviceStats(deviceStates, mTmpDeviceStatsArray);
+ }
+
+ /**
+ * This step is effectively a no-op in the cases where we track the same states for
+ * the entire device and all UIDs (e.g. screen on/off, on-battery/on-charger etc). However,
+ * if the lists of tracked states are not the same, we need to combine some estimates
+ * before distributing them proportionally to UIDs.
+ */
+ private void combineDeviceStateEstimates() {
+ for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) {
+ CombinedDeviceStateEstimate cdse = mPlan.combinedDeviceStateEstimations.get(i);
+ Intermediates cdseIntermediates = new Intermediates();
+ cdse.intermediates = cdseIntermediates;
+ List<DeviceStateEstimation> deviceStateEstimations = cdse.deviceStateEstimations;
+ for (int j = deviceStateEstimations.size() - 1; j >= 0; j--) {
+ DeviceStateEstimation dse = deviceStateEstimations.get(j);
+ Intermediates intermediates = (Intermediates) dse.intermediates;
+ cdseIntermediates.rxPower += intermediates.rxPower;
+ cdseIntermediates.txPower += intermediates.txPower;
+ cdseIntermediates.inactivePower += intermediates.inactivePower;
+ cdseIntermediates.consumedEnergy += intermediates.consumedEnergy;
+ }
+ }
+ }
+
+ private void computeUidRxTxTotals(PowerComponentAggregatedPowerStats stats, int uid,
+ UidStateEstimate uidStateEstimate) {
+ Intermediates intermediates =
+ (Intermediates) uidStateEstimate.combinedDeviceStateEstimate.intermediates;
+ for (UidStateProportionalEstimate proportionalEstimate :
+ uidStateEstimate.proportionalEstimates) {
+ if (!stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) {
+ continue;
+ }
+
+ intermediates.rxPackets += mStatsLayout.getUidRxPackets(mTmpUidStatsArray);
+ intermediates.txPackets += mStatsLayout.getUidTxPackets(mTmpUidStatsArray);
+ }
+ }
+
+ private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats, int uid,
+ UidStateEstimate uidStateEstimate) {
+ Intermediates intermediates =
+ (Intermediates) uidStateEstimate.combinedDeviceStateEstimate.intermediates;
+ for (UidStateProportionalEstimate proportionalEstimate :
+ uidStateEstimate.proportionalEstimates) {
+ if (!stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) {
+ continue;
+ }
+
+ double power = 0;
+ if (intermediates.rxPackets != 0) {
+ power += intermediates.rxPower * mStatsLayout.getUidRxPackets(mTmpUidStatsArray)
+ / intermediates.rxPackets;
+ }
+ if (intermediates.txPackets != 0) {
+ power += intermediates.txPower * mStatsLayout.getUidTxPackets(mTmpUidStatsArray)
+ / intermediates.txPackets;
+ }
+
+ mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+ stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+
+ if (DEBUG) {
+ Slog.d(TAG, "UID: " + uid
+ + " states: " + Arrays.toString(proportionalEstimate.stateValues)
+ + " stats: " + Arrays.toString(mTmpUidStatsArray)
+ + " rx: " + mStatsLayout.getUidRxPackets(mTmpUidStatsArray)
+ + " rx-power: " + intermediates.rxPower
+ + " rx-packets: " + intermediates.rxPackets
+ + " tx: " + mStatsLayout.getUidTxPackets(mTmpUidStatsArray)
+ + " tx-power: " + intermediates.txPower
+ + " tx-packets: " + intermediates.txPackets
+ + " power: " + power);
+ }
+ }
+ }
+
+ @Override
+ String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+ unpackPowerStatsDescriptor(descriptor);
+ return "idle: " + mStatsLayout.getDeviceIdleTime(stats)
+ + " sleep: " + mStatsLayout.getDeviceSleepTime(stats)
+ + " scan: " + mStatsLayout.getDeviceScanTime(stats)
+ + " power: " + mStatsLayout.getDevicePowerEstimate(stats);
+ }
+
+ @Override
+ String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
+ unpackPowerStatsDescriptor(descriptor);
+ StringBuilder sb = new StringBuilder();
+ sb.append(descriptor.getStateLabel(key));
+ sb.append(" rx: ").append(mStatsLayout.getStateRxTime(stats));
+ sb.append(" tx: ");
+ for (int txLevel = 0; txLevel < ModemActivityInfo.getNumTxPowerLevels(); txLevel++) {
+ if (txLevel != 0) {
+ sb.append(", ");
+ }
+ sb.append(mStatsLayout.getStateTxTime(stats, txLevel));
+ }
+ return sb.toString();
+ }
+
+ @Override
+ String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+ unpackPowerStatsDescriptor(descriptor);
+ return "rx: " + mStatsLayout.getUidRxPackets(stats)
+ + " tx: " + mStatsLayout.getUidTxPackets(stats)
+ + " power: " + mStatsLayout.getUidPowerEstimate(stats);
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/MultiStateStats.java b/services/core/java/com/android/server/power/stats/MultiStateStats.java
index 935695008a9a..6c4a2b6e6359 100644
--- a/services/core/java/com/android/server/power/stats/MultiStateStats.java
+++ b/services/core/java/com/android/server/power/stats/MultiStateStats.java
@@ -288,6 +288,14 @@ public class MultiStateStats {
}
/**
+ * Copies time-in-state and timestamps from the supplied prototype. Does not
+ * copy accumulated counts.
+ */
+ public void copyStatesFrom(MultiStateStats otherStats) {
+ mCounter.copyStatesFrom(otherStats.mCounter);
+ }
+
+ /**
* Updates the current composite state by changing one of the States supplied to the Factory
* constructor.
*
diff --git a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
new file mode 100644
index 000000000000..62b653f61373
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats;
+
+import android.os.BatteryConsumer;
+import android.os.PersistableBundle;
+
+import com.android.internal.os.PowerStats;
+
+public class PhoneCallPowerStatsProcessor extends PowerStatsProcessor {
+ private final PowerStatsLayout mStatsLayout;
+ private final PowerStats.Descriptor mDescriptor;
+ private final long[] mTmpDeviceStats;
+ private PowerStats.Descriptor mMobileRadioStatsDescriptor;
+ private MobileRadioPowerStatsLayout mMobileRadioStatsLayout;
+ private long[] mTmpMobileRadioDeviceStats;
+
+ public PhoneCallPowerStatsProcessor() {
+ mStatsLayout = new PowerStatsLayout();
+ mStatsLayout.addDeviceSectionPowerEstimate();
+ PersistableBundle extras = new PersistableBundle();
+ mStatsLayout.toExtras(extras);
+ mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_PHONE,
+ mStatsLayout.getDeviceStatsArrayLength(), null, 0, 0, extras);
+ mTmpDeviceStats = new long[mDescriptor.statsArrayLength];
+ }
+
+ @Override
+ void finish(PowerComponentAggregatedPowerStats stats) {
+ stats.setPowerStatsDescriptor(mDescriptor);
+
+ PowerComponentAggregatedPowerStats mobileRadioStats =
+ stats.getAggregatedPowerStats().getPowerComponentStats(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO);
+ if (mobileRadioStats == null) {
+ return;
+ }
+
+ if (mMobileRadioStatsDescriptor == null) {
+ mMobileRadioStatsDescriptor = mobileRadioStats.getPowerStatsDescriptor();
+ if (mMobileRadioStatsDescriptor == null) {
+ return;
+ }
+
+ mMobileRadioStatsLayout =
+ new MobileRadioPowerStatsLayout(
+ mMobileRadioStatsDescriptor);
+ mTmpMobileRadioDeviceStats = new long[mMobileRadioStatsDescriptor.statsArrayLength];
+ }
+
+ MultiStateStats.States[] deviceStateConfig =
+ mobileRadioStats.getConfig().getDeviceStateConfig();
+
+ // Phone call power estimates have already been calculated by the mobile radio stats
+ // processor. All that remains to be done is copy the estimates over.
+ MultiStateStats.States.forEachTrackedStateCombination(deviceStateConfig,
+ states -> {
+ mobileRadioStats.getDeviceStats(mTmpMobileRadioDeviceStats, states);
+ double callPowerEstimate =
+ mMobileRadioStatsLayout.getDeviceCallPowerEstimate(
+ mTmpMobileRadioDeviceStats);
+ mStatsLayout.setDevicePowerEstimate(mTmpDeviceStats, callPowerEstimate);
+ stats.setDeviceStats(states, mTmpDeviceStats);
+ });
+ }
+
+ @Override
+ String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+ return "power: " + mStatsLayout.getDevicePowerEstimate(stats);
+ }
+
+ @Override
+ String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
+ // Unsupported for this power component
+ return null;
+ }
+
+ @Override
+ String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+ // Unsupported for this power component
+ return null;
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 1637022f705d..6d58307dbefa 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -18,6 +18,7 @@ package com.android.server.power.stats;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
@@ -29,7 +30,10 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.function.IntConsumer;
/**
* Aggregated power stats for a specific power component (e.g. CPU, WiFi, etc). This class
@@ -41,22 +45,28 @@ class PowerComponentAggregatedPowerStats {
static final String XML_TAG_POWER_COMPONENT = "power_component";
static final String XML_ATTR_ID = "id";
private static final String XML_TAG_DEVICE_STATS = "device-stats";
+ private static final String XML_TAG_STATE_STATS = "state-stats";
+ private static final String XML_ATTR_KEY = "key";
private static final String XML_TAG_UID_STATS = "uid-stats";
private static final String XML_ATTR_UID = "uid";
private static final long UNKNOWN = -1;
public final int powerComponentId;
- private final MultiStateStats.States[] mDeviceStateConfig;
- private final MultiStateStats.States[] mUidStateConfig;
+ @NonNull
+ private final AggregatedPowerStats mAggregatedPowerStats;
@NonNull
private final AggregatedPowerStatsConfig.PowerComponent mConfig;
+ private final MultiStateStats.States[] mDeviceStateConfig;
+ private final MultiStateStats.States[] mUidStateConfig;
private final int[] mDeviceStates;
private MultiStateStats.Factory mStatsFactory;
+ private MultiStateStats.Factory mStateStatsFactory;
private MultiStateStats.Factory mUidStatsFactory;
private PowerStats.Descriptor mPowerStatsDescriptor;
private long mPowerStatsTimestamp;
private MultiStateStats mDeviceStats;
+ private final SparseArray<MultiStateStats> mStateStats = new SparseArray<>();
private final SparseArray<UidStats> mUidStats = new SparseArray<>();
private static class UidStats {
@@ -64,7 +74,9 @@ class PowerComponentAggregatedPowerStats {
public MultiStateStats stats;
}
- PowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) {
+ PowerComponentAggregatedPowerStats(@NonNull AggregatedPowerStats aggregatedPowerStats,
+ @NonNull AggregatedPowerStatsConfig.PowerComponent config) {
+ mAggregatedPowerStats = aggregatedPowerStats;
mConfig = config;
powerComponentId = config.getPowerComponentId();
mDeviceStateConfig = config.getDeviceStateConfig();
@@ -74,6 +86,11 @@ class PowerComponentAggregatedPowerStats {
}
@NonNull
+ AggregatedPowerStats getAggregatedPowerStats() {
+ return mAggregatedPowerStats;
+ }
+
+ @NonNull
public AggregatedPowerStatsConfig.PowerComponent getConfig() {
return mConfig;
}
@@ -83,16 +100,25 @@ class PowerComponentAggregatedPowerStats {
return mPowerStatsDescriptor;
}
- void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) {
+ public void setPowerStatsDescriptor(PowerStats.Descriptor powerStatsDescriptor) {
+ mPowerStatsDescriptor = powerStatsDescriptor;
+ }
+
+ void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state,
+ long timestampMs) {
if (mDeviceStats == null) {
- createDeviceStats();
+ createDeviceStats(timestampMs);
}
mDeviceStates[stateId] = state;
if (mDeviceStateConfig[stateId].isTracked()) {
if (mDeviceStats != null) {
- mDeviceStats.setState(stateId, state, time);
+ mDeviceStats.setState(stateId, state, timestampMs);
+ }
+ for (int i = mStateStats.size() - 1; i >= 0; i--) {
+ MultiStateStats stateStats = mStateStats.valueAt(i);
+ stateStats.setState(stateId, state, timestampMs);
}
}
@@ -100,36 +126,39 @@ class PowerComponentAggregatedPowerStats {
for (int i = mUidStats.size() - 1; i >= 0; i--) {
PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i);
if (uidStats.stats == null) {
- createUidStats(uidStats);
+ createUidStats(uidStats, timestampMs);
}
uidStats.states[stateId] = state;
if (uidStats.stats != null) {
- uidStats.stats.setState(stateId, state, time);
+ uidStats.stats.setState(stateId, state, timestampMs);
}
}
}
}
void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,
- long time) {
+ long timestampMs) {
if (!mUidStateConfig[stateId].isTracked()) {
return;
}
UidStats uidStats = getUidStats(uid);
if (uidStats.stats == null) {
- createUidStats(uidStats);
+ createUidStats(uidStats, timestampMs);
}
uidStats.states[stateId] = state;
if (uidStats.stats != null) {
- uidStats.stats.setState(stateId, state, time);
+ uidStats.stats.setState(stateId, state, timestampMs);
}
}
void setDeviceStats(@AggregatedPowerStatsConfig.TrackedState int[] states, long[] values) {
+ if (mDeviceStats == null) {
+ createDeviceStats(0);
+ }
mDeviceStats.setStats(states, values);
}
@@ -147,16 +176,24 @@ class PowerComponentAggregatedPowerStats {
mPowerStatsDescriptor = powerStats.descriptor;
if (mDeviceStats == null) {
- createDeviceStats();
+ createDeviceStats(timestampMs);
}
+ for (int i = powerStats.stateStats.size() - 1; i >= 0; i--) {
+ int key = powerStats.stateStats.keyAt(i);
+ MultiStateStats stateStats = mStateStats.get(key);
+ if (stateStats == null) {
+ stateStats = createStateStats(key, timestampMs);
+ }
+ stateStats.increment(powerStats.stateStats.valueAt(i), timestampMs);
+ }
mDeviceStats.increment(powerStats.stats, timestampMs);
for (int i = powerStats.uidStats.size() - 1; i >= 0; i--) {
int uid = powerStats.uidStats.keyAt(i);
PowerComponentAggregatedPowerStats.UidStats uidStats = getUidStats(uid);
if (uidStats.stats == null) {
- createUidStats(uidStats);
+ createUidStats(uidStats, timestampMs);
}
uidStats.stats.increment(powerStats.uidStats.valueAt(i), timestampMs);
}
@@ -168,6 +205,7 @@ class PowerComponentAggregatedPowerStats {
mStatsFactory = null;
mUidStatsFactory = null;
mDeviceStats = null;
+ mStateStats.clear();
for (int i = mUidStats.size() - 1; i >= 0; i--) {
mUidStats.valueAt(i).stats = null;
}
@@ -178,6 +216,13 @@ class PowerComponentAggregatedPowerStats {
if (uidStats == null) {
uidStats = new UidStats();
uidStats.states = new int[mUidStateConfig.length];
+ for (int stateId = 0; stateId < mUidStateConfig.length; stateId++) {
+ if (mUidStateConfig[stateId].isTracked()
+ && stateId < mDeviceStateConfig.length
+ && mDeviceStateConfig[stateId].isTracked()) {
+ uidStats.states[stateId] = mDeviceStates[stateId];
+ }
+ }
mUidStats.put(uid, uidStats);
}
return uidStats;
@@ -204,6 +249,26 @@ class PowerComponentAggregatedPowerStats {
return false;
}
+ boolean getStateStats(long[] outValues, int key, int[] deviceStates) {
+ if (deviceStates.length != mDeviceStateConfig.length) {
+ throw new IllegalArgumentException(
+ "Invalid number of tracked states: " + deviceStates.length
+ + " expected: " + mDeviceStateConfig.length);
+ }
+ MultiStateStats stateStats = mStateStats.get(key);
+ if (stateStats != null) {
+ stateStats.getStats(outValues, deviceStates);
+ return true;
+ }
+ return false;
+ }
+
+ void forEachStateStatsKey(IntConsumer consumer) {
+ for (int i = mStateStats.size() - 1; i >= 0; i--) {
+ consumer.accept(mStateStats.keyAt(i));
+ }
+ }
+
boolean getUidStats(long[] outValues, int uid, int[] uidStates) {
if (uidStates.length != mUidStateConfig.length) {
throw new IllegalArgumentException(
@@ -218,7 +283,7 @@ class PowerComponentAggregatedPowerStats {
return false;
}
- private void createDeviceStats() {
+ private void createDeviceStats(long timestampMs) {
if (mStatsFactory == null) {
if (mPowerStatsDescriptor == null) {
return;
@@ -229,13 +294,39 @@ class PowerComponentAggregatedPowerStats {
mDeviceStats = mStatsFactory.create();
if (mPowerStatsTimestamp != UNKNOWN) {
+ timestampMs = mPowerStatsTimestamp;
+ }
+ if (timestampMs != UNKNOWN) {
for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) {
- mDeviceStats.setState(stateId, mDeviceStates[stateId], mPowerStatsTimestamp);
+ int state = mDeviceStates[stateId];
+ mDeviceStats.setState(stateId, state, timestampMs);
+ for (int i = mStateStats.size() - 1; i >= 0; i--) {
+ MultiStateStats stateStats = mStateStats.valueAt(i);
+ stateStats.setState(stateId, state, timestampMs);
+ }
+ }
+ }
+ }
+
+ private MultiStateStats createStateStats(int key, long timestampMs) {
+ if (mStateStatsFactory == null) {
+ if (mPowerStatsDescriptor == null) {
+ return null;
}
+ mStateStatsFactory = new MultiStateStats.Factory(
+ mPowerStatsDescriptor.stateStatsArrayLength, mDeviceStateConfig);
}
+
+ MultiStateStats stateStats = mStateStatsFactory.create();
+ mStateStats.put(key, stateStats);
+ if (mDeviceStats != null) {
+ stateStats.copyStatesFrom(mDeviceStats);
+ }
+
+ return stateStats;
}
- private void createUidStats(UidStats uidStats) {
+ private void createUidStats(UidStats uidStats, long timestampMs) {
if (mUidStatsFactory == null) {
if (mPowerStatsDescriptor == null) {
return;
@@ -245,9 +336,13 @@ class PowerComponentAggregatedPowerStats {
}
uidStats.stats = mUidStatsFactory.create();
- for (int stateId = 0; stateId < mUidStateConfig.length; stateId++) {
- if (mPowerStatsTimestamp != UNKNOWN) {
- uidStats.stats.setState(stateId, uidStats.states[stateId], mPowerStatsTimestamp);
+
+ if (mPowerStatsTimestamp != UNKNOWN) {
+ timestampMs = mPowerStatsTimestamp;
+ }
+ if (timestampMs != UNKNOWN) {
+ for (int stateId = 0; stateId < mUidStateConfig.length; stateId++) {
+ uidStats.stats.setState(stateId, uidStats.states[stateId], timestampMs);
}
}
}
@@ -268,6 +363,13 @@ class PowerComponentAggregatedPowerStats {
serializer.endTag(null, XML_TAG_DEVICE_STATS);
}
+ for (int i = 0; i < mStateStats.size(); i++) {
+ serializer.startTag(null, XML_TAG_STATE_STATS);
+ serializer.attributeInt(null, XML_ATTR_KEY, mStateStats.keyAt(i));
+ mStateStats.valueAt(i).writeXml(serializer);
+ serializer.endTag(null, XML_TAG_STATE_STATS);
+ }
+
for (int i = mUidStats.size() - 1; i >= 0; i--) {
int uid = mUidStats.keyAt(i);
UidStats uidStats = mUidStats.valueAt(i);
@@ -285,8 +387,10 @@ class PowerComponentAggregatedPowerStats {
public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException,
IOException {
+ String outerTag = parser.getName();
int eventType = parser.getEventType();
- while (eventType != XmlPullParser.END_DOCUMENT) {
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG && parser.getName().equals(outerTag))) {
if (eventType == XmlPullParser.START_TAG) {
switch (parser.getName()) {
case PowerStats.Descriptor.XML_TAG_DESCRIPTOR:
@@ -297,17 +401,27 @@ class PowerComponentAggregatedPowerStats {
break;
case XML_TAG_DEVICE_STATS:
if (mDeviceStats == null) {
- createDeviceStats();
+ createDeviceStats(UNKNOWN);
}
if (!mDeviceStats.readFromXml(parser)) {
return false;
}
break;
+ case XML_TAG_STATE_STATS:
+ int key = parser.getAttributeInt(null, XML_ATTR_KEY);
+ MultiStateStats stats = mStateStats.get(key);
+ if (stats == null) {
+ stats = createStateStats(key, UNKNOWN);
+ }
+ if (!stats.readFromXml(parser)) {
+ return false;
+ }
+ break;
case XML_TAG_UID_STATS:
int uid = parser.getAttributeInt(null, XML_ATTR_UID);
UidStats uidStats = getUidStats(uid);
if (uidStats.stats == null) {
- createUidStats(uidStats);
+ createUidStats(uidStats, UNKNOWN);
}
if (!uidStats.stats.readFromXml(parser)) {
return false;
@@ -328,6 +442,21 @@ class PowerComponentAggregatedPowerStats {
mConfig.getProcessor().deviceStatsToString(mPowerStatsDescriptor, stats));
ipw.decreaseIndent();
}
+
+ if (mStateStats.size() != 0) {
+ ipw.increaseIndent();
+ ipw.println(mPowerStatsDescriptor.name + " states");
+ ipw.increaseIndent();
+ for (int i = 0; i < mStateStats.size(); i++) {
+ int key = mStateStats.keyAt(i);
+ MultiStateStats stateStats = mStateStats.valueAt(i);
+ stateStats.dump(ipw, stats ->
+ mConfig.getProcessor().stateStatsToString(mPowerStatsDescriptor, key,
+ stats));
+ }
+ ipw.decreaseIndent();
+ ipw.decreaseIndent();
+ }
}
void dumpUid(IndentingPrintWriter ipw, int uid) {
@@ -340,4 +469,29 @@ class PowerComponentAggregatedPowerStats {
ipw.decreaseIndent();
}
}
+
+ @Override
+ public String toString() {
+ StringWriter sw = new StringWriter();
+ IndentingPrintWriter ipw = new IndentingPrintWriter(sw);
+ ipw.increaseIndent();
+ dumpDevice(ipw);
+ ipw.decreaseIndent();
+
+ int[] uids = new int[mUidStats.size()];
+ for (int i = uids.length - 1; i >= 0; i--) {
+ uids[i] = mUidStats.keyAt(i);
+ }
+ Arrays.sort(uids);
+ for (int uid : uids) {
+ ipw.println(UserHandle.formatUid(uid));
+ ipw.increaseIndent();
+ dumpUid(ipw, uid);
+ ipw.decreaseIndent();
+ }
+
+ ipw.flush();
+
+ return sw.toString();
+ }
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
index ba4c127ac3d0..6a4c1f0406a9 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -32,7 +32,7 @@ public class PowerStatsAggregator {
private static final long UNINITIALIZED = -1;
private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
private final BatteryStatsHistory mHistory;
- private final SparseArray<AggregatedPowerStatsProcessor> mProcessors = new SparseArray<>();
+ private final SparseArray<PowerStatsProcessor> mProcessors = new SparseArray<>();
private AggregatedPowerStats mStats;
private int mCurrentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
private int mCurrentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
@@ -43,7 +43,7 @@ public class PowerStatsAggregator {
mHistory = history;
for (AggregatedPowerStatsConfig.PowerComponent powerComponentsConfig :
aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs()) {
- AggregatedPowerStatsProcessor processor = powerComponentsConfig.getProcessor();
+ PowerStatsProcessor processor = powerComponentsConfig.getProcessor();
mProcessors.put(powerComponentsConfig.getPowerComponentId(), processor);
}
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index c76797bad66d..5dd11db2a2fc 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -17,9 +17,12 @@
package com.android.server.power.stats;
import android.annotation.Nullable;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
import android.os.ConditionVariable;
import android.os.Handler;
-import android.os.PersistableBundle;
+import android.power.PowerStatsInternal;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -30,7 +33,12 @@ import com.android.internal.os.PowerStats;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
/**
@@ -43,6 +51,7 @@ import java.util.function.Consumer;
public abstract class PowerStatsCollector {
private static final String TAG = "PowerStatsCollector";
private static final int MILLIVOLTS_PER_VOLT = 1000;
+ private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
private final Handler mHandler;
protected final Clock mClock;
private final long mThrottlePeriodMs;
@@ -50,200 +59,6 @@ public abstract class PowerStatsCollector {
private boolean mEnabled;
private long mLastScheduledUpdateMs = -1;
- /**
- * Captures the positions and lengths of sections of the stats array, such as usage duration,
- * power usage estimates etc.
- */
- public static class StatsArrayLayout {
- private static final String EXTRA_DEVICE_POWER_POSITION = "dp";
- private static final String EXTRA_DEVICE_DURATION_POSITION = "dd";
- private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de";
- private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec";
- private static final String EXTRA_UID_POWER_POSITION = "up";
-
- protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0;
-
- private int mDeviceStatsArrayLength;
- private int mUidStatsArrayLength;
-
- protected int mDeviceDurationPosition;
- private int mDeviceEnergyConsumerPosition;
- private int mDeviceEnergyConsumerCount;
- private int mDevicePowerEstimatePosition;
- private int mUidPowerEstimatePosition;
-
- public int getDeviceStatsArrayLength() {
- return mDeviceStatsArrayLength;
- }
-
- public int getUidStatsArrayLength() {
- return mUidStatsArrayLength;
- }
-
- protected int addDeviceSection(int length) {
- int position = mDeviceStatsArrayLength;
- mDeviceStatsArrayLength += length;
- return position;
- }
-
- protected int addUidSection(int length) {
- int position = mUidStatsArrayLength;
- mUidStatsArrayLength += length;
- return position;
- }
-
- /**
- * Declare that the stats array has a section capturing usage duration
- */
- public void addDeviceSectionUsageDuration() {
- mDeviceDurationPosition = addDeviceSection(1);
- }
-
- /**
- * Saves the usage duration in the corresponding <code>stats</code> element.
- */
- public void setUsageDuration(long[] stats, long value) {
- stats[mDeviceDurationPosition] = value;
- }
-
- /**
- * Extracts the usage duration from the corresponding <code>stats</code> element.
- */
- public long getUsageDuration(long[] stats) {
- return stats[mDeviceDurationPosition];
- }
-
- /**
- * Declares that the stats array has a section capturing EnergyConsumer data from
- * PowerStatsService.
- */
- public void addDeviceSectionEnergyConsumers(int energyConsumerCount) {
- mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount);
- mDeviceEnergyConsumerCount = energyConsumerCount;
- }
-
- public int getEnergyConsumerCount() {
- return mDeviceEnergyConsumerCount;
- }
-
- /**
- * Saves the accumulated energy for the specified rail the corresponding
- * <code>stats</code> element.
- */
- public void setConsumedEnergy(long[] stats, int index, long energy) {
- stats[mDeviceEnergyConsumerPosition + index] = energy;
- }
-
- /**
- * Extracts the EnergyConsumer data from a device stats array for the specified
- * EnergyConsumer.
- */
- public long getConsumedEnergy(long[] stats, int index) {
- return stats[mDeviceEnergyConsumerPosition + index];
- }
-
- /**
- * Declare that the stats array has a section capturing a power estimate
- */
- public void addDeviceSectionPowerEstimate() {
- mDevicePowerEstimatePosition = addDeviceSection(1);
- }
-
- /**
- * Converts the supplied mAh power estimate to a long and saves it in the corresponding
- * element of <code>stats</code>.
- */
- public void setDevicePowerEstimate(long[] stats, double power) {
- stats[mDevicePowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
- }
-
- /**
- * Extracts the power estimate from a device stats array and converts it to mAh.
- */
- public double getDevicePowerEstimate(long[] stats) {
- return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
- }
-
- /**
- * Declare that the UID stats array has a section capturing a power estimate
- */
- public void addUidSectionPowerEstimate() {
- mUidPowerEstimatePosition = addUidSection(1);
- }
-
- /**
- * Converts the supplied mAh power estimate to a long and saves it in the corresponding
- * element of <code>stats</code>.
- */
- public void setUidPowerEstimate(long[] stats, double power) {
- stats[mUidPowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
- }
-
- /**
- * Extracts the power estimate from a UID stats array and converts it to mAh.
- */
- public double getUidPowerEstimate(long[] stats) {
- return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
- }
-
- /**
- * Copies the elements of the stats array layout into <code>extras</code>
- */
- public void toExtras(PersistableBundle extras) {
- extras.putInt(EXTRA_DEVICE_DURATION_POSITION, mDeviceDurationPosition);
- extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION,
- mDeviceEnergyConsumerPosition);
- extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT,
- mDeviceEnergyConsumerCount);
- extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition);
- extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition);
- }
-
- /**
- * Retrieves elements of the stats array layout from <code>extras</code>
- */
- public void fromExtras(PersistableBundle extras) {
- mDeviceDurationPosition = extras.getInt(EXTRA_DEVICE_DURATION_POSITION);
- mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION);
- mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT);
- mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION);
- mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION);
- }
-
- protected void putIntArray(PersistableBundle extras, String key, int[] array) {
- if (array == null) {
- return;
- }
-
- StringBuilder sb = new StringBuilder();
- for (int value : array) {
- if (!sb.isEmpty()) {
- sb.append(',');
- }
- sb.append(value);
- }
- extras.putString(key, sb.toString());
- }
-
- protected int[] getIntArray(PersistableBundle extras, String key) {
- String string = extras.getString(key);
- if (string == null) {
- return null;
- }
- String[] values = string.trim().split(",");
- int[] result = new int[values.length];
- for (int i = 0; i < values.length; i++) {
- try {
- result[i] = Integer.parseInt(values[i]);
- } catch (NumberFormatException e) {
- Slog.wtf(TAG, "Invalid CSV format: " + string);
- return null;
- }
- }
- return result;
- }
- }
-
@GuardedBy("this")
@SuppressWarnings("unchecked")
private volatile List<Consumer<PowerStats>> mConsumerList = Collections.emptyList();
@@ -389,9 +204,83 @@ public abstract class PowerStatsCollector {
}
/** Calculate charge consumption (in microcoulombs) from a given energy and voltage */
- protected long uJtoUc(long deltaEnergyUj, int avgVoltageMv) {
+ protected static long uJtoUc(long deltaEnergyUj, int avgVoltageMv) {
// To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times
// since the last snapshot. Round off to the nearest whole long.
return (deltaEnergyUj * MILLIVOLTS_PER_VOLT + (avgVoltageMv / 2)) / avgVoltageMv;
}
+
+ interface ConsumedEnergyRetriever {
+ int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType);
+
+ @Nullable
+ long[] getConsumedEnergyUws(int[] energyConsumerIds);
+ }
+
+ static class ConsumedEnergyRetrieverImpl implements ConsumedEnergyRetriever {
+ private final PowerStatsInternal mPowerStatsInternal;
+
+ ConsumedEnergyRetrieverImpl(PowerStatsInternal powerStatsInternal) {
+ mPowerStatsInternal = powerStatsInternal;
+ }
+
+ @Override
+ public int[] getEnergyConsumerIds(int energyConsumerType) {
+ if (mPowerStatsInternal == null) {
+ return new int[0];
+ }
+
+ EnergyConsumer[] energyConsumerInfo = mPowerStatsInternal.getEnergyConsumerInfo();
+ if (energyConsumerInfo == null) {
+ return new int[0];
+ }
+
+ List<EnergyConsumer> energyConsumers = new ArrayList<>();
+ for (EnergyConsumer energyConsumer : energyConsumerInfo) {
+ if (energyConsumer.type == energyConsumerType) {
+ energyConsumers.add(energyConsumer);
+ }
+ }
+ if (energyConsumers.isEmpty()) {
+ return new int[0];
+ }
+
+ energyConsumers.sort(Comparator.comparing(c -> c.ordinal));
+
+ int[] ids = new int[energyConsumers.size()];
+ for (int i = 0; i < ids.length; i++) {
+ ids[i] = energyConsumers.get(i).id;
+ }
+ return ids;
+ }
+
+ @Override
+ public long[] getConsumedEnergyUws(int[] energyConsumerIds) {
+ CompletableFuture<EnergyConsumerResult[]> future =
+ mPowerStatsInternal.getEnergyConsumedAsync(energyConsumerIds);
+ EnergyConsumerResult[] results = null;
+ try {
+ results = future.get(
+ POWER_STATS_ENERGY_CONSUMERS_TIMEOUT, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ Slog.e(TAG, "Could not obtain energy consumers from PowerStatsService", e);
+ }
+
+ if (results == null) {
+ return null;
+ }
+
+ long[] energy = new long[energyConsumerIds.length];
+ for (int i = 0; i < energyConsumerIds.length; i++) {
+ int id = energyConsumerIds[i];
+ for (EnergyConsumerResult result : results) {
+ if (result.id == id) {
+ energy[i] = result.energyUWs;
+ break;
+ }
+ }
+ }
+ return energy;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 4f4ddca6c3fc..f6b198a88fc2 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -139,7 +139,7 @@ public class PowerStatsExporter {
return;
}
- PowerStatsCollector.StatsArrayLayout layout = new PowerStatsCollector.StatsArrayLayout();
+ PowerStatsLayout layout = new PowerStatsLayout();
layout.fromExtras(descriptor.extras);
long[] deviceStats = new long[descriptor.statsArrayLength];
@@ -164,9 +164,20 @@ public class PowerStatsExporter {
deviceScope.addConsumedPower(powerComponentId,
totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED);
+ if (layout.isUidPowerAttributionSupported()) {
+ populateUidBatteryConsumers(batteryUsageStatsBuilder, powerComponent,
+ powerComponentStats, layout);
+ }
+ }
+
+ private static void populateUidBatteryConsumers(
+ BatteryUsageStats.Builder batteryUsageStatsBuilder,
+ AggregatedPowerStatsConfig.PowerComponent powerComponent,
+ PowerComponentAggregatedPowerStats powerComponentStats,
+ PowerStatsLayout layout) {
+ int powerComponentId = powerComponent.getPowerComponentId();
+ PowerStats.Descriptor descriptor = powerComponentStats.getPowerStatsDescriptor();
long[] uidStats = new long[descriptor.uidStatsArrayLength];
- ArrayList<Integer> uids = new ArrayList<>();
- powerComponentStats.collectUids(uids);
boolean breakDownByProcState =
batteryUsageStatsBuilder.isProcessStateDataNeeded()
@@ -177,6 +188,8 @@ public class PowerStatsExporter {
double[] powerByProcState =
new double[breakDownByProcState ? BatteryConsumer.PROCESS_STATE_COUNT : 1];
double powerAllApps = 0;
+ ArrayList<Integer> uids = new ArrayList<>();
+ powerComponentStats.collectUids(uids);
for (int uid : uids) {
UidBatteryConsumer.Builder builder =
batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid);
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
new file mode 100644
index 000000000000..aa96409e85e9
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
@@ -0,0 +1,243 @@
+/*
+ * 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.power.stats;
+
+import android.os.PersistableBundle;
+import android.util.Slog;
+
+import com.android.internal.os.PowerStats;
+
+/**
+ * Captures the positions and lengths of sections of the stats array, such as usage duration,
+ * power usage estimates etc.
+ */
+public class PowerStatsLayout {
+ private static final String TAG = "PowerStatsLayout";
+ private static final String EXTRA_DEVICE_POWER_POSITION = "dp";
+ private static final String EXTRA_DEVICE_DURATION_POSITION = "dd";
+ private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de";
+ private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec";
+ private static final String EXTRA_UID_POWER_POSITION = "up";
+
+ protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0;
+ protected static final int UNSUPPORTED = -1;
+
+ private int mDeviceStatsArrayLength;
+ private int mStateStatsArrayLength;
+ private int mUidStatsArrayLength;
+
+ protected int mDeviceDurationPosition = UNSUPPORTED;
+ private int mDeviceEnergyConsumerPosition;
+ private int mDeviceEnergyConsumerCount;
+ private int mDevicePowerEstimatePosition = UNSUPPORTED;
+ private int mUidPowerEstimatePosition = UNSUPPORTED;
+
+ public PowerStatsLayout() {
+ }
+
+ public PowerStatsLayout(PowerStats.Descriptor descriptor) {
+ fromExtras(descriptor.extras);
+ }
+
+ public int getDeviceStatsArrayLength() {
+ return mDeviceStatsArrayLength;
+ }
+
+ public int getStateStatsArrayLength() {
+ return mStateStatsArrayLength;
+ }
+
+ public int getUidStatsArrayLength() {
+ return mUidStatsArrayLength;
+ }
+
+ protected int addDeviceSection(int length) {
+ int position = mDeviceStatsArrayLength;
+ mDeviceStatsArrayLength += length;
+ return position;
+ }
+
+ protected int addStateSection(int length) {
+ int position = mStateStatsArrayLength;
+ mStateStatsArrayLength += length;
+ return position;
+ }
+
+ protected int addUidSection(int length) {
+ int position = mUidStatsArrayLength;
+ mUidStatsArrayLength += length;
+ return position;
+ }
+
+ /**
+ * Declare that the stats array has a section capturing usage duration
+ */
+ public void addDeviceSectionUsageDuration() {
+ mDeviceDurationPosition = addDeviceSection(1);
+ }
+
+ /**
+ * Saves the usage duration in the corresponding <code>stats</code> element.
+ */
+ public void setUsageDuration(long[] stats, long value) {
+ stats[mDeviceDurationPosition] = value;
+ }
+
+ /**
+ * Extracts the usage duration from the corresponding <code>stats</code> element.
+ */
+ public long getUsageDuration(long[] stats) {
+ return stats[mDeviceDurationPosition];
+ }
+
+ /**
+ * Declares that the stats array has a section capturing EnergyConsumer data from
+ * PowerStatsService.
+ */
+ public void addDeviceSectionEnergyConsumers(int energyConsumerCount) {
+ mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount);
+ mDeviceEnergyConsumerCount = energyConsumerCount;
+ }
+
+ public int getEnergyConsumerCount() {
+ return mDeviceEnergyConsumerCount;
+ }
+
+ /**
+ * Saves the accumulated energy for the specified rail the corresponding
+ * <code>stats</code> element.
+ */
+ public void setConsumedEnergy(long[] stats, int index, long energy) {
+ stats[mDeviceEnergyConsumerPosition + index] = energy;
+ }
+
+ /**
+ * Extracts the EnergyConsumer data from a device stats array for the specified
+ * EnergyConsumer.
+ */
+ public long getConsumedEnergy(long[] stats, int index) {
+ return stats[mDeviceEnergyConsumerPosition + index];
+ }
+
+ /**
+ * Declare that the stats array has a section capturing a power estimate
+ */
+ public void addDeviceSectionPowerEstimate() {
+ mDevicePowerEstimatePosition = addDeviceSection(1);
+ }
+
+ /**
+ * Converts the supplied mAh power estimate to a long and saves it in the corresponding
+ * element of <code>stats</code>.
+ */
+ public void setDevicePowerEstimate(long[] stats, double power) {
+ stats[mDevicePowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
+ }
+
+ /**
+ * Extracts the power estimate from a device stats array and converts it to mAh.
+ */
+ public double getDevicePowerEstimate(long[] stats) {
+ return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
+ }
+
+ /**
+ * Declare that the UID stats array has a section capturing a power estimate
+ */
+ public void addUidSectionPowerEstimate() {
+ mUidPowerEstimatePosition = addUidSection(1);
+ }
+
+ /**
+ * Returns true if power for this component is attributed to UIDs (apps).
+ */
+ public boolean isUidPowerAttributionSupported() {
+ return mUidPowerEstimatePosition != UNSUPPORTED;
+ }
+
+ /**
+ * Converts the supplied mAh power estimate to a long and saves it in the corresponding
+ * element of <code>stats</code>.
+ */
+ public void setUidPowerEstimate(long[] stats, double power) {
+ stats[mUidPowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
+ }
+
+ /**
+ * Extracts the power estimate from a UID stats array and converts it to mAh.
+ */
+ public double getUidPowerEstimate(long[] stats) {
+ return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
+ }
+
+ /**
+ * Copies the elements of the stats array layout into <code>extras</code>
+ */
+ public void toExtras(PersistableBundle extras) {
+ extras.putInt(EXTRA_DEVICE_DURATION_POSITION, mDeviceDurationPosition);
+ extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION,
+ mDeviceEnergyConsumerPosition);
+ extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT,
+ mDeviceEnergyConsumerCount);
+ extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition);
+ extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition);
+ }
+
+ /**
+ * Retrieves elements of the stats array layout from <code>extras</code>
+ */
+ public void fromExtras(PersistableBundle extras) {
+ mDeviceDurationPosition = extras.getInt(EXTRA_DEVICE_DURATION_POSITION);
+ mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION);
+ mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT);
+ mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION);
+ mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION);
+ }
+
+ protected void putIntArray(PersistableBundle extras, String key, int[] array) {
+ if (array == null) {
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (int value : array) {
+ if (!sb.isEmpty()) {
+ sb.append(',');
+ }
+ sb.append(value);
+ }
+ extras.putString(key, sb.toString());
+ }
+
+ protected int[] getIntArray(PersistableBundle extras, String key) {
+ String string = extras.getString(key);
+ if (string == null) {
+ return null;
+ }
+ String[] values = string.trim().split(",");
+ int[] result = new int[values.length];
+ for (int i = 0; i < values.length; i++) {
+ try {
+ result[i] = Integer.parseInt(values[i]);
+ } catch (NumberFormatException e) {
+ Slog.wtf(TAG, "Invalid CSV format: " + string);
+ return null;
+ }
+ }
+ return result;
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
index 7feb9643fb8f..0d5c5422b45c 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
@@ -27,7 +27,7 @@ import java.util.Collections;
import java.util.List;
/*
- * The power estimation algorithm used by AggregatedPowerStatsProcessor can roughly be
+ * The power estimation algorithm used by PowerStatsProcessor can roughly be
* described like this:
*
* 1. Estimate power usage for each state combination (e.g. power-battery/screen-on) using
@@ -39,8 +39,8 @@ import java.util.List;
* 2. For each UID, compute the proportion of the combined estimates in each state
* and attribute the corresponding portion of the total power estimate in that state to the UID.
*/
-abstract class AggregatedPowerStatsProcessor {
- private static final String TAG = "AggregatedPowerStatsProcessor";
+abstract class PowerStatsProcessor {
+ private static final String TAG = "PowerStatsProcessor";
private static final int INDEX_DOES_NOT_EXIST = -1;
private static final double MILLIAMPHOUR_PER_MICROCOULOMB = 1.0 / 1000.0 / 60.0 / 60.0;
@@ -49,6 +49,8 @@ abstract class AggregatedPowerStatsProcessor {
abstract String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats);
+ abstract String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats);
+
abstract String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats);
protected static class PowerEstimationPlan {
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 9f0a97523af8..8f99d2836c63 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -568,23 +568,25 @@ public class PowerStatsService extends SystemService {
int index = 0;
Channel[] channels = getEnergyMeterInfo();
- for (Channel channel : channels) {
- PowerMonitor monitor = new PowerMonitor(index++,
- PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT,
- getChannelName(channel));
- monitors.add(monitor);
- states.add(new PowerMonitorState(monitor, channel.id));
+ if (channels != null) {
+ for (Channel channel : channels) {
+ PowerMonitor monitor = new PowerMonitor(index++,
+ PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT,
+ getChannelName(channel));
+ monitors.add(monitor);
+ states.add(new PowerMonitorState(monitor, channel.id));
+ }
}
-
EnergyConsumer[] energyConsumers = getEnergyConsumerInfo();
- for (EnergyConsumer consumer : energyConsumers) {
- PowerMonitor monitor = new PowerMonitor(index++,
- PowerMonitor.POWER_MONITOR_TYPE_CONSUMER,
- getEnergyConsumerName(consumer, energyConsumers));
- monitors.add(monitor);
- states.add(new PowerMonitorState(monitor, consumer.id));
+ if (energyConsumers != null) {
+ for (EnergyConsumer consumer : energyConsumers) {
+ PowerMonitor monitor = new PowerMonitor(index++,
+ PowerMonitor.POWER_MONITOR_TYPE_CONSUMER,
+ getEnergyConsumerName(consumer, energyConsumers));
+ monitors.add(monitor);
+ states.add(new PowerMonitorState(monitor, consumer.id));
+ }
}
-
mPowerMonitors = monitors.toArray(new PowerMonitor[monitors.size()]);
mPowerMonitorStates = states.toArray(new PowerMonitorState[monitors.size()]);
}
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index 3c0547edbd92..c24240b92289 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -30,6 +30,7 @@ import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_
import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_NO_PROVIDER;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.apex.CompressedApexInfo;
import android.apex.CompressedApexInfoList;
import android.content.Context;
@@ -37,6 +38,7 @@ import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.hardware.boot.IBootControl;
+import android.hardware.security.secretkeeper.ISecretkeeper;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Binder;
@@ -52,6 +54,7 @@ import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.SystemProperties;
import android.provider.DeviceConfig;
+import android.security.AndroidKeyStoreMaintenance;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.FastImmutableArraySet;
@@ -68,6 +71,7 @@ import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.pm.ApexManager;
import com.android.server.recoverysystem.hal.BootControlHIDL;
+import com.android.server.utils.Slogf;
import libcore.io.IoUtils;
@@ -122,6 +126,8 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
static final String LSKF_CAPTURED_TIMESTAMP_PREF = "lskf_captured_timestamp";
static final String LSKF_CAPTURED_COUNT_PREF = "lskf_captured_count";
+ static final String RECOVERY_WIPE_DATA_COMMAND = "--wipe_data";
+
private final Injector mInjector;
private final Context mContext;
@@ -525,18 +531,57 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
@Override // Binder call
public void rebootRecoveryWithCommand(String command) {
if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
+
+ boolean isForcedWipe = command != null && command.contains(RECOVERY_WIPE_DATA_COMMAND);
synchronized (sRequestLock) {
if (!setupOrClearBcb(true, command)) {
Slog.e(TAG, "rebootRecoveryWithCommand failed to setup BCB");
return;
}
+ if (isForcedWipe) {
+ deleteSecrets();
+ // TODO: consider adding a dedicated forced-wipe-reboot method to PowerManager and
+ // calling here.
+ }
+
// Having set up the BCB, go ahead and reboot.
PowerManager pm = mInjector.getPowerManager();
pm.reboot(PowerManager.REBOOT_RECOVERY);
}
}
+ private static void deleteSecrets() {
+ Slogf.w(TAG, "deleteSecrets");
+ try {
+ AndroidKeyStoreMaintenance.deleteAllKeys();
+ } catch (android.security.KeyStoreException e) {
+ Log.wtf(TAG, "Failed to delete all keys from keystore.", e);
+ }
+
+ try {
+ ISecretkeeper secretKeeper = getSecretKeeper();
+ if (secretKeeper != null) {
+ Slogf.i(TAG, "ISecretkeeper.deleteAll();");
+ secretKeeper.deleteAll();
+ }
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Failed to delete all secrets from secretkeeper.", e);
+ }
+ }
+
+ private static @Nullable ISecretkeeper getSecretKeeper() {
+ ISecretkeeper result = null;
+ try {
+ result = ISecretkeeper.Stub.asInterface(
+ ServiceManager.waitForDeclaredService(ISecretkeeper.DESCRIPTOR + "/default"));
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Does not have permissions to get AIDL secretkeeper service");
+ }
+
+ return result;
+ }
+
private void enforcePermissionForResumeOnReboot() {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.RECOVERY)
!= PackageManager.PERMISSION_GRANTED
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 2a9325544833..c8bcc5128c3a 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -54,8 +54,10 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.ext.SdkExtensions;
import android.provider.DeviceConfig;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.LongArrayQueue;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@@ -173,6 +175,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
// Accessed on the handler thread only.
private long mRelativeBootTime = calculateRelativeBootTime();
+ private final ArrayMap<Integer, Pair<Context, BroadcastReceiver>> mUserBroadcastReceivers;
+
RollbackManagerServiceImpl(Context context) {
mContext = context;
// Note that we're calling onStart here because this object is only constructed on
@@ -210,6 +214,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
}
});
+ mUserBroadcastReceivers = new ArrayMap<>();
+
UserManager userManager = mContext.getSystemService(UserManager.class);
for (UserHandle user : userManager.getUserHandles(true)) {
registerUserCallbacks(user);
@@ -275,7 +281,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
}
}, enableRollbackTimedOutFilter, null, getHandler());
- IntentFilter userAddedIntentFilter = new IntentFilter(Intent.ACTION_USER_ADDED);
+ IntentFilter userIntentFilter = new IntentFilter();
+ userIntentFilter.addAction(Intent.ACTION_USER_ADDED);
+ userIntentFilter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -287,9 +295,15 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
return;
}
registerUserCallbacks(UserHandle.of(newUserId));
+ } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+ final int newUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (newUserId == -1) {
+ return;
+ }
+ unregisterUserCallbacks(UserHandle.of(newUserId));
}
}
- }, userAddedIntentFilter, null, getHandler());
+ }, userIntentFilter, null, getHandler());
registerTimeChangeReceiver();
}
@@ -335,7 +349,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
filter.addDataScheme("package");
- context.registerReceiver(new BroadcastReceiver() {
+ BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
assertInWorkerThread();
@@ -354,7 +368,21 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
onPackageFullyRemoved(packageName);
}
}
- }, filter, null, getHandler());
+ };
+ context.registerReceiver(receiver, filter, null, getHandler());
+ mUserBroadcastReceivers.put(user.getIdentifier(), new Pair(context, receiver));
+ }
+
+ @AnyThread
+ private void unregisterUserCallbacks(UserHandle user) {
+ Pair<Context, BroadcastReceiver> pair = mUserBroadcastReceivers.get(user.getIdentifier());
+ if (pair == null || pair.first == null || pair.second == null) {
+ Slog.e(TAG, "No receiver found for the user" + user);
+ return;
+ }
+
+ pair.first.unregisterReceiver(pair.second);
+ mUserBroadcastReceivers.remove(user.getIdentifier());
}
@ExtThread
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 96f045d7e258..8138168f609e 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -44,6 +44,8 @@ public final class HapticFeedbackVibrationProvider {
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_PHYSICAL_EMULATION);
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
+ private static final VibrationAttributes COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
private final VibratorInfo mVibratorInfo;
private final boolean mHapticTextHandleEnabled;
@@ -120,7 +122,6 @@ public final class HapticFeedbackVibrationProvider {
return getKeyboardVibration(effectId);
case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
- case HapticFeedbackConstants.ENTRY_BUMP:
case HapticFeedbackConstants.DRAG_CROSSING:
return getVibration(
effectId,
@@ -131,6 +132,7 @@ public final class HapticFeedbackVibrationProvider {
case HapticFeedbackConstants.EDGE_RELEASE:
case HapticFeedbackConstants.CALENDAR_DATE:
case HapticFeedbackConstants.CONFIRM:
+ case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
case HapticFeedbackConstants.GESTURE_START:
case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
case HapticFeedbackConstants.SCROLL_LIMIT:
@@ -143,6 +145,7 @@ public final class HapticFeedbackVibrationProvider {
return getVibration(effectId, VibrationEffect.EFFECT_HEAVY_CLICK);
case HapticFeedbackConstants.REJECT:
+ case HapticFeedbackConstants.BIOMETRIC_REJECT:
return getVibration(effectId, VibrationEffect.EFFECT_DOUBLE_CLICK);
case HapticFeedbackConstants.SAFE_MODE_ENABLED:
@@ -207,6 +210,10 @@ public final class HapticFeedbackVibrationProvider {
case HapticFeedbackConstants.KEYBOARD_RELEASE:
attrs = createKeyboardVibrationAttributes(fromIme);
break;
+ case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
+ case HapticFeedbackConstants.BIOMETRIC_REJECT:
+ attrs = COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES;
+ break;
default:
attrs = TOUCH_VIBRATION_ATTRIBUTES;
}
@@ -225,6 +232,23 @@ public final class HapticFeedbackVibrationProvider {
return flags == 0 ? attrs : new VibrationAttributes.Builder(attrs).setFlags(flags).build();
}
+ /**
+ * Returns true if given haptic feedback is restricted to system apps with permission
+ * {@code android.permission.VIBRATE_SYSTEM_CONSTANTS}.
+ *
+ * @param effectId the haptic feedback effect ID to check.
+ * @return true if the haptic feedback is restricted, false otherwise.
+ */
+ public boolean isRestrictedHapticFeedback(int effectId) {
+ switch (effectId) {
+ case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
+ case HapticFeedbackConstants.BIOMETRIC_REJECT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
/** Dumps relevant state. */
public void dump(String prefix, PrintWriter pw) {
pw.print("mHapticTextHandleEnabled="); pw.println(mHapticTextHandleEnabled);
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 9e9025e35e60..8281ac1c9d28 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -439,6 +439,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
Slog.w(TAG, "performHapticFeedback; haptic vibration provider not ready.");
return null;
}
+ if (hapticVibrationProvider.isRestrictedHapticFeedback(constant)
+ && !hasPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS)) {
+ Slog.w(TAG, "performHapticFeedback; no permission for effect " + constant);
+ return null;
+ }
VibrationEffect effect = hapticVibrationProvider.getVibrationForHapticFeedback(constant);
if (effect == null) {
Slog.w(TAG, "performHapticFeedback; vibration absent for effect " + constant);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 6905802809c3..f6e0168fd236 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -137,6 +137,27 @@ public class WallpaperCropper {
// This will fall into "Case 4" of this function and center the folded screen
return getCrop(displaySize, bitmapSize, newSuggestedCrops, rtl);
}
+
+ // The second exception is if we're on tablet and we're on portrait mode.
+ // In that case, center the wallpaper relatively to landscape and put some parallax.
+ boolean isTablet = mWallpaperDisplayHelper.isLargeScreen()
+ && !mWallpaperDisplayHelper.isFoldable();
+ if (isTablet && displaySize.x < displaySize.y) {
+ Point rotatedDisplaySize = new Point(displaySize.y, displaySize.x);
+ // compute the crop on landscape (without parallax)
+ Rect landscapeCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl);
+ landscapeCrop = noParallax(landscapeCrop, rotatedDisplaySize, bitmapSize, rtl);
+ // compute the crop on portrait at the center of the landscape crop
+ crop = getAdjustedCrop(landscapeCrop, bitmapSize, displaySize, false, rtl, ADD);
+
+ // add some parallax (until the border of the landscape crop without parallax)
+ if (rtl) {
+ crop.left = landscapeCrop.left;
+ } else {
+ crop.right = landscapeCrop.right;
+ }
+ }
+
return getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
}
@@ -472,6 +493,7 @@ public class WallpaperCropper {
}
}
final Rect cropHint;
+ final SparseArray<Rect> defaultCrops;
// A wallpaper with cropHints = Map.of(ORIENTATION_UNKNOWN, rect) is treated like
// a wallpaper with cropHints = null and cropHint = rect.
@@ -483,7 +505,7 @@ public class WallpaperCropper {
if (multiCrop() && wallpaper.mCropHints.size() > 0) {
// Some suggested crops per screen orientation were provided,
// use them to compute the default crops for this device
- SparseArray<Rect> defaultCrops = getDefaultCrops(wallpaper.mCropHints, bitmapSize);
+ defaultCrops = getDefaultCrops(wallpaper.mCropHints, bitmapSize);
// Adapt the provided crops to match the actual crops for the default display
SparseArray<Rect> updatedCropHints = new SparseArray<>();
for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
@@ -514,7 +536,7 @@ public class WallpaperCropper {
wallpaper.cropHint.set(bitmapRect);
}
Point cropSize = new Point(wallpaper.cropHint.width(), wallpaper.cropHint.height());
- SparseArray<Rect> defaultCrops = getDefaultCrops(new SparseArray<>(), cropSize);
+ defaultCrops = getDefaultCrops(new SparseArray<>(), cropSize);
cropHint = getTotalCrop(defaultCrops);
cropHint.offset(wallpaper.cropHint.left, wallpaper.cropHint.top);
wallpaper.cropHint.set(cropHint);
@@ -523,6 +545,7 @@ public class WallpaperCropper {
}
} else {
cropHint = new Rect(wallpaper.cropHint);
+ defaultCrops = null;
}
if (DEBUG) {
@@ -563,6 +586,33 @@ public class WallpaperCropper {
|| cropHint.height() > GLHelper.getMaxTextureSize()
|| cropHint.width() > GLHelper.getMaxTextureSize();
+ float sampleSize = Float.MAX_VALUE;
+ if (multiCrop()) {
+ // If all crops for all orientations have more width and height in pixel
+ // than the display for this orientation, downsample the image
+ for (int i = 0; i < defaultCrops.size(); i++) {
+ int orientation = defaultCrops.keyAt(i);
+ Rect crop = defaultCrops.valueAt(i);
+ Point displayForThisOrientation = mWallpaperDisplayHelper
+ .getDefaultDisplaySizes().get(orientation);
+ if (displayForThisOrientation == null) continue;
+ float sampleSizeForThisOrientation = Math.max(1f, Math.min(
+ crop.width() / displayForThisOrientation.x,
+ crop.height() / displayForThisOrientation.y));
+ sampleSize = Math.min(sampleSize, sampleSizeForThisOrientation);
+ }
+ // If the total crop has more width or height than either the max texture size
+ // or twice the largest display dimension, downsample the image
+ int maxCropSize = Math.min(
+ 2 * mWallpaperDisplayHelper.getDefaultDisplayLargestDimension(),
+ GLHelper.getMaxTextureSize());
+ float minimumSampleSize = Math.max(1f, Math.max(
+ (float) cropHint.height() / maxCropSize,
+ (float) cropHint.width()) / maxCropSize);
+ sampleSize = Math.max(sampleSize, minimumSampleSize);
+ needScale = sampleSize > 1f;
+ }
+
//make sure screen aspect ratio is preserved if width is scaled under screen size
if (needScale && !multiCrop()) {
final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height();
@@ -577,7 +627,8 @@ public class WallpaperCropper {
if (DEBUG_CROP) {
Slog.v(TAG, "crop: w=" + cropHint.width() + " h=" + cropHint.height());
- Slog.v(TAG, "dims: w=" + wpData.mWidth + " h=" + wpData.mHeight);
+ if (multiCrop()) Slog.v(TAG, "defaultCrops: " + defaultCrops);
+ if (!multiCrop()) Slog.v(TAG, "dims: w=" + wpData.mWidth + " h=" + wpData.mHeight);
Slog.v(TAG, "meas: w=" + options.outWidth + " h=" + options.outHeight);
Slog.v(TAG, "crop?=" + needCrop + " scale?=" + needScale);
}
@@ -620,28 +671,17 @@ public class WallpaperCropper {
options.inJustDecodeBounds = false;
final Rect estimateCrop = new Rect(cropHint);
- estimateCrop.scale(1f / options.inSampleSize);
+ if (!multiCrop()) estimateCrop.scale(1f / options.inSampleSize);
+ else estimateCrop.scale(1f / sampleSize);
float hRatio = (float) wpData.mHeight / estimateCrop.height();
- if (multiCrop()) {
- // make sure the crop height is at most the display largest dimension
- hRatio = (float) mWallpaperDisplayHelper.getDefaultDisplayLargestDimension()
- / estimateCrop.height();
- hRatio = Math.min(hRatio, 1f);
- }
final int destHeight = (int) (estimateCrop.height() * hRatio);
final int destWidth = (int) (estimateCrop.width() * hRatio);
// We estimated an invalid crop, try to adjust the cropHint to get a valid one.
- if (destWidth > GLHelper.getMaxTextureSize()) {
+ if (!multiCrop() && destWidth > GLHelper.getMaxTextureSize()) {
if (DEBUG) {
Slog.w(TAG, "Invalid crop dimensions, trying to adjust.");
}
- if (multiCrop()) {
- // clear custom crop guidelines, fallback to system default
- wallpaper.mCropHints.clear();
- generateCropInternal(wallpaper);
- return;
- }
int newHeight = (int) (wpData.mHeight / hRatio);
int newWidth = (int) (wpData.mWidth / hRatio);
@@ -658,16 +698,27 @@ public class WallpaperCropper {
// We've got the safe cropHint; now we want to scale it properly to
// the desired rectangle.
// That's a height-biased operation: make it fit the hinted height.
- final int safeHeight = (int) (estimateCrop.height() * hRatio + 0.5f);
- final int safeWidth = (int) (estimateCrop.width() * hRatio + 0.5f);
+ final int safeHeight = !multiCrop()
+ ? (int) (estimateCrop.height() * hRatio + 0.5f)
+ : (int) (cropHint.height() / sampleSize + 0.5f);
+ final int safeWidth = !multiCrop()
+ ? (int) (estimateCrop.width() * hRatio + 0.5f)
+ : (int) (cropHint.width() / sampleSize + 0.5f);
if (DEBUG_CROP) {
Slog.v(TAG, "Decode parameters:");
- Slog.v(TAG, " cropHint=" + cropHint + ", estimateCrop=" + estimateCrop);
- Slog.v(TAG, " down sampling=" + options.inSampleSize
- + ", hRatio=" + hRatio);
- Slog.v(TAG, " dest=" + destWidth + "x" + destHeight);
- Slog.v(TAG, " safe=" + safeWidth + "x" + safeHeight);
+ if (!multiCrop()) {
+ Slog.v(TAG,
+ " cropHint=" + cropHint + ", estimateCrop=" + estimateCrop);
+ Slog.v(TAG, " down sampling=" + options.inSampleSize
+ + ", hRatio=" + hRatio);
+ Slog.v(TAG, " dest=" + destWidth + "x" + destHeight);
+ }
+ if (multiCrop()) {
+ Slog.v(TAG, " cropHint=" + cropHint);
+ Slog.v(TAG, " sampleSize=" + sampleSize);
+ }
+ Slog.v(TAG, " targetSize=" + safeWidth + "x" + safeHeight);
Slog.v(TAG, " maxTextureSize=" + GLHelper.getMaxTextureSize());
}
@@ -682,24 +733,28 @@ public class WallpaperCropper {
final ImageDecoder.Source srcData =
ImageDecoder.createSource(wallpaper.getWallpaperFile());
- final int sampleSize = scale;
+ final int finalScale = scale;
+ final int rescaledBitmapWidth = (int) (0.5f + bitmapSize.x / sampleSize);
+ final int rescaledBitmapHeight = (int) (0.5f + bitmapSize.y / sampleSize);
Bitmap cropped = ImageDecoder.decodeBitmap(srcData, (decoder, info, src) -> {
- decoder.setTargetSampleSize(sampleSize);
+ if (!multiCrop()) decoder.setTargetSampleSize(finalScale);
+ if (multiCrop()) {
+ decoder.setTargetSize(rescaledBitmapWidth, rescaledBitmapHeight);
+ }
decoder.setCrop(estimateCrop);
});
record.delete();
- if (cropped == null) {
+ if (!multiCrop() && cropped == null) {
Slog.e(TAG, "Could not decode new wallpaper");
} else {
// We are safe to create final crop with safe dimensions now.
- final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped,
- safeWidth, safeHeight, true);
+ final Bitmap finalCrop = multiCrop() ? cropped
+ : Bitmap.createScaledBitmap(cropped, safeWidth, safeHeight, true);
if (multiCrop()) {
- wallpaper.mSampleSize =
- ((float) cropHint.height()) / finalCrop.getHeight();
+ wallpaper.mSampleSize = sampleSize;
}
if (DEBUG) {
@@ -718,9 +773,7 @@ public class WallpaperCropper {
success = true;
}
} catch (Exception e) {
- if (DEBUG) {
- Slog.e(TAG, "Error decoding crop", e);
- }
+ Slog.e(TAG, "Error decoding crop", e);
} finally {
IoUtils.closeQuietly(bos);
IoUtils.closeQuietly(f);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
index 9e1b5d238d48..3636f5aa8f27 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -59,6 +59,8 @@ class WallpaperDisplayHelper {
}
private static final String TAG = WallpaperDisplayHelper.class.getSimpleName();
+ private static final float LARGE_SCREEN_MIN_DP = 600f;
+
private final SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
private final DisplayManager mDisplayManager;
private final WindowManagerInternal mWindowManagerInternal;
@@ -67,7 +69,8 @@ class WallpaperDisplayHelper {
// related orientations pairs for foldable (folded orientation, unfolded orientation)
private final List<Pair<Integer, Integer>> mFoldableOrientationPairs = new ArrayList<>();
- private boolean mIsFoldable;
+ private final boolean mIsFoldable;
+ private boolean mIsLargeScreen = false;
WallpaperDisplayHelper(
DisplayManager displayManager,
@@ -94,6 +97,9 @@ class WallpaperDisplayHelper {
mDefaultDisplaySizes.put(orientation, point);
}
}
+
+ mIsLargeScreen |= (displaySize.x / metric.getDensity() >= LARGE_SCREEN_MIN_DP);
+
if (populateOrientationPairs) {
int orientation = WallpaperManager.getOrientation(displaySize);
float newSurface = displaySize.x * displaySize.y
@@ -215,6 +221,13 @@ class WallpaperDisplayHelper {
}
/**
+ * Return true if any of the screens of the default display is considered large (DP >= 600)
+ */
+ boolean isLargeScreen() {
+ return mIsLargeScreen;
+ }
+
+ /**
* If a given orientation corresponds to an unfolded orientation on foldable, return the
* corresponding folded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the
* device is not a foldable.
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 8c4c0de71781..f1ba755836b2 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -42,6 +42,7 @@ import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_ORIG;
import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
import static com.android.window.flags.Flags.multiCrop;
+import static com.android.window.flags.Flags.offloadColorExtraction;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -131,6 +132,9 @@ import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wallpaper.WallpaperData.BindSource;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
import org.xmlpull.v1.XmlPullParserException;
@@ -166,6 +170,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
@Override
+ @UsesReflection(
+ value = {
+ @KeepTarget(
+ kind = KeepItemKind.CLASS_AND_MEMBERS,
+ instanceOfClassConstantExclusive = IWallpaperManagerService.class,
+ methodName = "<init>")
+ })
public void onStart() {
try {
final Class<? extends IWallpaperManagerService> klass =
@@ -380,7 +391,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
// Outside of the lock since it will synchronize itself
- notifyWallpaperColorsChanged(wallpaper);
+ if (!offloadColorExtraction()) notifyWallpaperColorsChanged(wallpaper);
}
@Override
@@ -403,12 +414,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper) {
+ notifyWallpaperColorsChanged(wallpaper, wallpaper.mWhich);
+ }
+
+ private void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
if (DEBUG) {
Slog.i(TAG, "Notifying wallpaper colors changed");
}
if (wallpaper.connection != null) {
wallpaper.connection.forEachDisplayConnector(connector ->
- notifyWallpaperColorsChangedOnDisplay(wallpaper, connector.mDisplayId));
+ notifyWallpaperColorsChangedOnDisplay(wallpaper, connector.mDisplayId, which));
}
}
@@ -425,6 +440,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper,
int displayId) {
+ notifyWallpaperColorsChangedOnDisplay(wallpaper, displayId, wallpaper.mWhich);
+ }
+
+ private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper,
+ int displayId, int which) {
boolean needsExtraction;
synchronized (mLock) {
final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners =
@@ -449,8 +469,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
notify = extractColors(wallpaper);
}
if (notify) {
- notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper),
- wallpaper.mWhich, wallpaper.userId, displayId);
+ notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper), which,
+ wallpaper.userId, displayId);
}
}
@@ -504,6 +524,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
* @return true unless the wallpaper changed during the color computation
*/
private boolean extractColors(WallpaperData wallpaper) {
+ if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.wallpaperComponent);
String cropFile = null;
boolean defaultImageWallpaper = false;
int wallpaperId;
@@ -1148,10 +1169,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
synchronized (mLock) {
// Do not broadcast changes on ImageWallpaper since it's handled
// internally by this class.
- if (mImageWallpaper.equals(mWallpaper.wallpaperComponent)) {
+ boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.wallpaperComponent);
+ if (isImageWallpaper && (!offloadColorExtraction() || primaryColors == null)) {
return;
}
mWallpaper.primaryColors = primaryColors;
+ // only save the colors for ImageWallpaper - for live wallpapers, the colors
+ // are always recomputed after a reboot.
+ if (offloadColorExtraction() && isImageWallpaper) {
+ saveSettingsLocked(mWallpaper.userId);
+ }
}
notifyWallpaperColorsChangedOnDisplay(mWallpaper, displayId);
}
@@ -1177,7 +1204,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
try {
// This will trigger onComputeColors in the wallpaper engine.
// It's fine to be locked in here since the binder is oneway.
- connector.mEngine.requestWallpaperColors();
+ if (!offloadColorExtraction() || mWallpaper.primaryColors == null) {
+ connector.mEngine.requestWallpaperColors();
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Failed to request wallpaper colors", e);
}
@@ -1811,6 +1840,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
// Offload color extraction to another thread since switchUser will be called
// from the main thread.
FgThread.getHandler().post(() -> {
+ if (offloadColorExtraction()) return;
notifyWallpaperColorsChanged(systemWallpaper);
if (lockWallpaper != systemWallpaper) notifyWallpaperColorsChanged(lockWallpaper);
notifyWallpaperColorsChanged(mFallbackWallpaper);
@@ -2239,6 +2269,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Point croppedBitmapSize = new Point(
(int) (0.5f + wallpaper.cropHint.width() / wallpaper.mSampleSize),
(int) (0.5f + wallpaper.cropHint.height() / wallpaper.mSampleSize));
+ if (croppedBitmapSize.equals(0, 0)) {
+ // There is an ImageWallpaper, but there are no crop hints and the bitmap size is
+ // unknown (e.g. the default wallpaper). Return a special "null" value that will be
+ // handled by WallpaperManager, which will fetch the dimensions of the wallpaper.
+ return null;
+ }
SparseArray<Rect> relativeDefaultCrops =
mWallpaperCropper.getDefaultCrops(relativeSuggestedCrops, croppedBitmapSize);
SparseArray<Rect> adjustedRelativeSuggestedCrops = new SparseArray<>();
@@ -2722,8 +2758,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
});
// Need to extract colors again to re-calculate dark hints after
// applying dimming.
- wp.mIsColorExtractedFromDim = true;
- pendingColorExtraction.add(wp);
+ if (!offloadColorExtraction()) {
+ wp.mIsColorExtractedFromDim = true;
+ pendingColorExtraction.add(wp);
+ }
changed = true;
}
}
@@ -2732,7 +2770,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
for (WallpaperData wp: pendingColorExtraction) {
- notifyWallpaperColorsChanged(wp);
+ if (!offloadColorExtraction()) notifyWallpaperColorsChanged(wp);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -2927,6 +2965,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
wallpaper.allowBackup = allowBackup;
wallpaper.mWallpaperDimAmount = getWallpaperDimAmount();
+ if (offloadColorExtraction()) wallpaper.primaryColors = null;
}
return pfd;
} finally {
@@ -3069,6 +3108,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
boolean shouldNotifyColors = false;
+
+ // If the lockscreen wallpaper is set to the same as the home screen, notify that the
+ // lockscreen wallpaper colors changed, even if we don't bind a new wallpaper engine.
+ boolean shouldNotifyLockscreenColors = false;
boolean bindSuccess;
final WallpaperData newWallpaper;
@@ -3114,7 +3157,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
bindSuccess = bindWallpaperComponentLocked(name, /* force */
forceRebind, /* fromUser */ true, newWallpaper, reply);
if (bindSuccess) {
- if (!same) {
+ if (!same || (offloadColorExtraction() && forceRebind)) {
newWallpaper.primaryColors = null;
} else {
if (newWallpaper.connection != null) {
@@ -3138,6 +3181,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
newWallpaper.wallpaperId = makeWallpaperIdLocked();
notifyCallbacksLocked(newWallpaper);
shouldNotifyColors = true;
+ if (offloadColorExtraction()) {
+ shouldNotifyColors = false;
+ shouldNotifyLockscreenColors = !force && same && !systemIsBoth
+ && which == (FLAG_SYSTEM | FLAG_LOCK);
+ }
if (which == (FLAG_SYSTEM | FLAG_LOCK)) {
if (DEBUG) {
@@ -3166,6 +3214,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (shouldNotifyColors) {
notifyWallpaperColorsChanged(newWallpaper);
}
+ if (shouldNotifyLockscreenColors) {
+ notifyWallpaperColorsChanged(newWallpaper, FLAG_LOCK);
+ }
+
return bindSuccess;
}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
index eb170b702eb9..36e52008f223 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
@@ -17,6 +17,9 @@
package com.android.server.wearable;
import static android.service.wearable.WearableSensingService.HOTWORD_AUDIO_STREAM_BUNDLE_KEY;
+import static android.system.OsConstants.F_GETFL;
+import static android.system.OsConstants.O_ACCMODE;
+import static android.system.OsConstants.O_RDONLY;
import android.Manifest;
import android.annotation.NonNull;
@@ -42,6 +45,8 @@ import android.os.SharedMemory;
import android.service.voice.HotwordAudioStream;
import android.service.voice.VoiceInteractionManagerInternal;
import android.service.voice.VoiceInteractionManagerInternal.WearableHotwordDetectionCallback;
+import android.system.ErrnoException;
+import android.system.Os;
import android.system.OsConstants;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -276,7 +281,10 @@ final class WearableSensingManagerPerUserService extends
ParcelFileDescriptor parcelFileDescriptor,
@Nullable IWearableSensingCallback wearableSensingCallback,
RemoteCallback statusCallback) {
- Slog.i(TAG, "onProvideDataStream in per user service.");
+ Slog.i(
+ TAG,
+ "onProvideDataStream in per user service. Is data stream read-only? "
+ + isReadOnly(parcelFileDescriptor));
synchronized (mLock) {
if (!setUpServiceIfNeeded()) {
Slog.w(TAG, "Detection service is not available at this moment.");
@@ -505,10 +513,53 @@ final class WearableSensingManagerPerUserService extends
String filename,
AndroidFuture<ParcelFileDescriptor> futureFromWearableSensingService)
throws RemoteException {
- // TODO(b/331395522): Intercept the PFD received from the app process and verify it
- // is read-only
- callbackFromAppProcess.openFile(filename, futureFromWearableSensingService);
+ AndroidFuture<ParcelFileDescriptor> futureFromSystemServer =
+ new AndroidFuture<ParcelFileDescriptor>()
+ .whenComplete(
+ (pfdFromApp, throwable) -> {
+ if (throwable != null) {
+ Slog.e(
+ TAG,
+ "Error when reading file " + filename,
+ throwable);
+ futureFromWearableSensingService.complete(null);
+ return;
+ }
+ if (pfdFromApp == null) {
+ futureFromWearableSensingService.complete(null);
+ return;
+ }
+ if (isReadOnly(pfdFromApp)) {
+ futureFromWearableSensingService.complete(
+ pfdFromApp);
+ } else {
+ Slog.w(
+ TAG,
+ "Received writable ParcelFileDescriptor"
+ + " from app process. To prevent"
+ + " arbitrary data egress, sending null"
+ + " to WearableSensingService"
+ + " instead.");
+ futureFromWearableSensingService.complete(null);
+ }
+ });
+ callbackFromAppProcess.openFile(filename, futureFromSystemServer);
}
};
}
+
+ private static boolean isReadOnly(ParcelFileDescriptor parcelFileDescriptor) {
+ try {
+ int readMode =
+ Os.fcntlInt(parcelFileDescriptor.getFileDescriptor(), F_GETFL, 0) & O_ACCMODE;
+ return readMode == O_RDONLY;
+ } catch (ErrnoException ex) {
+ Slog.w(
+ TAG,
+ "Error encountered when trying to determine if the parcelFileDescriptor is"
+ + " read-only. Treating it as not read-only",
+ ex);
+ }
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/webkit/flags.aconfig b/services/core/java/com/android/server/webkit/flags.aconfig
index 84dc1d7ca831..2afbcd6f101d 100644
--- a/services/core/java/com/android/server/webkit/flags.aconfig
+++ b/services/core/java/com/android/server/webkit/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.webkit"
-container: "system"
flag {
name: "update_service_v2"
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4036d553c82f..a0902cdd7c27 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8544,7 +8544,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
// If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
// are already calculated in resolveFixedOrientationConfiguration.
- } else if (!isLetterboxedForFixedOrientationAndAspectRatio()) {
+ // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
+ } else if (!isLetterboxedForFixedOrientationAndAspectRatio()
+ && !mLetterboxUiController.hasFullscreenOverride()) {
resolveAspectRatioRestriction(newParentConfiguration);
}
@@ -8645,7 +8647,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Override starts here.
final Rect stableInsets = mDisplayContent.getDisplayPolicy().getDecorInsetsInfo(
- rotation, fullBounds.width(), fullBounds.height()).mLegacyConfigInsets;
+ rotation, fullBounds.width(), fullBounds.height()).mOverrideConfigInsets;
// This should be the only place override the configuration for ActivityRecord. Override
// the value if not calculated yet.
Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
@@ -8681,7 +8683,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mDisplayContent.getDisplay().getDisplayInfo(info);
mDisplayContent.computeSizeRanges(info, rotated, info.logicalWidth,
info.logicalHeight, mDisplayContent.getDisplayMetrics().density,
- inOutConfig, true /* legacyConfig */);
+ inOutConfig, true /* overrideConfig */);
}
// It's possible that screen size will be considered in different orientation with or
@@ -8965,8 +8967,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
: mDisplayContent.getDisplayInfo();
final Task task = getTask();
task.calculateInsetFrames(mTmpBounds /* outNonDecorBounds */,
- outStableBounds /* outStableBounds */, parentBounds /* bounds */, di,
- true /* useLegacyInsetsForStableBounds */);
+ outStableBounds /* outStableBounds */, parentBounds /* bounds */, di);
final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
// If orientation does not match the orientation with insets applied, then a
@@ -9060,8 +9061,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// vertically centered within parent bounds with insets, so position vertical bounds
// within parent bounds with insets to prevent insets from unnecessarily trimming
// vertical bounds.
- final int bottom = Math.min(parentBoundsWithInsets.top + parentBounds.width() - 1,
- parentBoundsWithInsets.bottom);
+ final int bottom = Math.min(parentBoundsWithInsets.top
+ + parentBoundsWithInsets.width() - 1, parentBoundsWithInsets.bottom);
containingBounds.set(parentBounds.left, parentBoundsWithInsets.top, parentBounds.right,
bottom);
containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top,
@@ -9072,8 +9073,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// horizontally centered within parent bounds with insets, so position horizontal bounds
// within parent bounds with insets to prevent insets from unnecessarily trimming
// horizontal bounds.
- final int right = Math.min(parentBoundsWithInsets.left + parentBounds.height(),
- parentBoundsWithInsets.right);
+ final int right = Math.min(parentBoundsWithInsets.left
+ + parentBoundsWithInsets.height(), parentBoundsWithInsets.right);
containingBounds.set(parentBoundsWithInsets.left, parentBounds.top, right,
parentBounds.bottom);
containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top,
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index f6681c571090..b9979adbafd0 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -20,6 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -173,27 +174,6 @@ class BackNavigationController {
}
}
- // This is needed to bridge the old and new back behavior with recents. While in
- // Overview with live tile enabled, the previous app is technically focused but we
- // add an input consumer to capture all input that would otherwise go to the apps
- // being controlled by the animation. This means that the window resolved is not
- // the right window to consume back while in overview, so we need to route it to
- // launcher and use the legacy behavior of injecting KEYCODE_BACK since the existing
- // compat callback in VRI only works when the window is focused.
- // This symptom also happen while shell transition enabled, we can check that by
- // isTransientLaunch to know whether the focus window is point to live tile.
- final RecentsAnimationController recentsAnimationController =
- wmService.getRecentsAnimationController();
- final ActivityRecord tmpAR = window.mActivityRecord;
- if ((tmpAR != null && tmpAR.isActivityTypeHomeOrRecents()
- && tmpAR.mTransitionController.isTransientLaunch(tmpAR))
- || (recentsAnimationController != null
- && recentsAnimationController.shouldApplyInputConsumer(tmpAR))) {
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Current focused window being animated by "
- + "recents. Overriding back callback to recents controller callback.");
- return null;
- }
-
if (!window.isDrawn()) {
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Focused window didn't have a valid surface drawn.");
@@ -779,6 +759,10 @@ class BackNavigationController {
&& wc.asTaskFragment() == null) {
continue;
}
+ // Only care if visibility changed.
+ if (targets.get(i).getTransitMode(wc) == TRANSIT_CHANGE) {
+ continue;
+ }
// WC can be visible due to setLaunchBehind
if (wc.isVisibleRequested()) {
mTmpOpenApps.add(wc);
@@ -843,14 +827,14 @@ class BackNavigationController {
* @param targets The final animation targets derived in transition.
* @param finishedTransition The finished transition target.
*/
- boolean onTransitionFinish(ArrayList<Transition.ChangeInfo> targets,
+ void onTransitionFinish(ArrayList<Transition.ChangeInfo> targets,
@NonNull Transition finishedTransition) {
if (finishedTransition == mWaitTransitionFinish) {
clearBackAnimations();
}
if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) {
- return false;
+ return;
}
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Handling the deferred animation after transition finished");
@@ -878,7 +862,7 @@ class BackNavigationController {
+ " open: " + Arrays.toString(mPendingAnimationBuilder.mOpenTargets)
+ " close: " + mPendingAnimationBuilder.mCloseTarget);
cancelPendingAnimation();
- return false;
+ return;
}
// Ensure the final animation targets which hidden by transition could be visible.
@@ -887,9 +871,14 @@ class BackNavigationController {
wc.prepareSurfaces();
}
- scheduleAnimation(mPendingAnimationBuilder);
- mPendingAnimationBuilder = null;
- return true;
+ // The pending builder could be cleared due to prepareSurfaces
+ // => updateNonSystemOverlayWindowsVisibilityIfNeeded
+ // => setForceHideNonSystemOverlayWindowIfNeeded
+ // => updateFocusedWindowLocked => onFocusWindowChanged.
+ if (mPendingAnimationBuilder != null) {
+ scheduleAnimation(mPendingAnimationBuilder);
+ mPendingAnimationBuilder = null;
+ }
}
private void cancelPendingAnimation() {
@@ -1552,15 +1541,17 @@ class BackNavigationController {
return this;
}
+ // WC must be Activity/TaskFragment/Task
boolean containTarget(@NonNull WindowContainer wc) {
if (mOpenTargets != null) {
for (int i = mOpenTargets.length - 1; i >= 0; --i) {
- if (wc == mOpenTargets[i] || mOpenTargets[i].hasChild(wc)) {
+ if (wc == mOpenTargets[i] || mOpenTargets[i].hasChild(wc)
+ || wc.hasChild(mOpenTargets[i])) {
return true;
}
}
}
- return wc == mCloseTarget || mCloseTarget.hasChild(wc);
+ return wc == mCloseTarget || mCloseTarget.hasChild(wc) || wc.hasChild(mCloseTarget);
}
/**
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 47f4a66995af..0e446b8eaf8c 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -38,9 +38,11 @@ import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
+import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg;
import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
import static com.android.window.flags.Flags.balRequireOptInSameUid;
+import static com.android.window.flags.Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid;
import static com.android.window.flags.Flags.balShowToastsBlocked;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -539,6 +541,16 @@ public class BackgroundActivityStartController {
sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender);
sb.append("; resultIfPiSenderAllowsBal: ").append(mResultForRealCaller);
}
+ // features
+ sb.append("; balImproveRealCallerVisibilityCheck: ")
+ .append(balImproveRealCallerVisibilityCheck());
+ sb.append("; balRequireOptInByPendingIntentCreator: ")
+ .append(balRequireOptInByPendingIntentCreator());
+ sb.append("; balRequireOptInSameUid: ").append(balRequireOptInSameUid());
+ sb.append("; balRespectAppSwitchStateWhenCheckBoundByForegroundUid: ")
+ .append(balRespectAppSwitchStateWhenCheckBoundByForegroundUid());
+ sb.append("; balDontBringExistingBackgroundTaskStackToFg: ")
+ .append(balDontBringExistingBackgroundTaskStackToFg());
sb.append("]");
return sb.toString();
}
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index efeb85f623d1..d49a507c9e11 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -40,6 +40,10 @@ import android.content.res.Resources;
import android.os.Bundle;
import android.text.TextUtils;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+
import java.util.ArrayList;
import java.util.List;
@@ -184,6 +188,13 @@ public abstract class DisplayAreaPolicy {
/**
* Instantiates the device-specific {@link Provider}.
*/
+ @UsesReflection(
+ value = {
+ @KeepTarget(
+ kind = KeepItemKind.CLASS_AND_MEMBERS,
+ instanceOfClassConstantExclusive = Provider.class,
+ methodName = "<init>")
+ })
static Provider fromResources(Resources res) {
String name = res.getString(
com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index cde3e68e43c9..739f76e2bdc7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1007,8 +1007,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;
- final boolean obscuredChanged = w.mObscured !=
- mTmpApplySurfaceChangesTransactionState.obscured;
final RootWindowContainer root = mWmService.mRoot;
if (w.mHasSurface) {
@@ -1107,12 +1105,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
- if (obscuredChanged && w.isVisible() && mWallpaperController.isWallpaperTarget(w)) {
- // This is the wallpaper target and its obscured state changed... make sure the
- // current wallpaper's visibility has been updated accordingly.
- mWallpaperController.updateWallpaperTokens(mDisplayContent.isKeyguardLocked());
- }
-
w.handleWindowMovedIfNeeded();
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing");
@@ -2277,7 +2269,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
computeSizeRanges(mDisplayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig,
- false /* legacyConfig */);
+ false /* overrideConfig */);
setDisplayInfoOverride();
@@ -2414,7 +2406,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
displayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
computeSizeRanges(displayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig,
- false /* legacyConfig */);
+ false /* overrideConfig */);
return displayInfo;
}
@@ -2588,11 +2580,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* @param dh Display Height in current rotation.
* @param density Display density.
* @param outConfig The output configuration to
- * @param legacyConfig Whether we need to report the legacy result, which is excluding system
- * decorations.
+ * @param overrideConfig Whether we need to report the override config result
*/
void computeSizeRanges(DisplayInfo displayInfo, boolean rotated,
- int dw, int dh, float density, Configuration outConfig, boolean legacyConfig) {
+ int dw, int dh, float density, Configuration outConfig, boolean overrideConfig) {
// We need to determine the smallest width that will occur under normal
// operation. To this, start with the base screen size and compute the
@@ -2610,10 +2601,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
displayInfo.smallestNominalAppHeight = 1<<30;
displayInfo.largestNominalAppWidth = 0;
displayInfo.largestNominalAppHeight = 0;
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh, legacyConfig);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw, legacyConfig);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh, legacyConfig);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw, legacyConfig);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh, overrideConfig);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw, overrideConfig);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh,
+ overrideConfig);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw,
+ overrideConfig);
if (outConfig == null) {
return;
@@ -2623,17 +2616,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh,
- boolean legacyConfig) {
+ boolean overrideConfig) {
final DisplayPolicy.DecorInsets.Info info = mDisplayPolicy.getDecorInsetsInfo(
rotation, dw, dh);
final int w;
final int h;
- if (!legacyConfig) {
+ if (!overrideConfig) {
w = info.mConfigFrame.width();
h = info.mConfigFrame.height();
} else {
- w = info.mLegacyConfigFrame.width();
- h = info.mLegacyConfigFrame.height();
+ w = info.mOverrideConfigFrame.width();
+ h = info.mOverrideConfigFrame.height();
}
if (w < displayInfo.smallestNominalAppWidth) {
displayInfo.smallestNominalAppWidth = w;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index a5037ea0ff07..5e0d4f9a33f9 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1935,9 +1935,9 @@ public class DisplayPolicy {
final Rect mConfigInsets = new Rect();
/**
- * Legacy value of mConfigInsets for app compatibility purpose.
+ * Override value of mConfigInsets for app compatibility purpose.
*/
- final Rect mLegacyConfigInsets = new Rect();
+ final Rect mOverrideConfigInsets = new Rect();
/** The display frame available after excluding {@link #mNonDecorInsets}. */
final Rect mNonDecorFrame = new Rect();
@@ -1950,9 +1950,9 @@ public class DisplayPolicy {
final Rect mConfigFrame = new Rect();
/**
- * Legacy value of mConfigFrame for app compatibility purpose.
+ * Override value of mConfigFrame for app compatibility purpose.
*/
- final Rect mLegacyConfigFrame = new Rect();
+ final Rect mOverrideConfigFrame = new Rect();
private boolean mNeedUpdate = true;
@@ -1968,22 +1968,22 @@ public class DisplayPolicy {
? decor
: insetsState.calculateInsets(displayFrame, dc.mWmService.mConfigTypes,
true /* ignoreVisibility */);
- final Insets legacyConfigInsets = dc.mWmService.mConfigTypes
- == dc.mWmService.mLegacyConfigTypes
+ final Insets overrideConfigInsets = dc.mWmService.mConfigTypes
+ == dc.mWmService.mOverrideConfigTypes
? configInsets
: insetsState.calculateInsets(displayFrame,
- dc.mWmService.mLegacyConfigTypes, true /* ignoreVisibility */);
+ dc.mWmService.mOverrideConfigTypes, true /* ignoreVisibility */);
mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
mConfigInsets.set(configInsets.left, configInsets.top, configInsets.right,
configInsets.bottom);
- mLegacyConfigInsets.set(legacyConfigInsets.left, legacyConfigInsets.top,
- legacyConfigInsets.right, legacyConfigInsets.bottom);
+ mOverrideConfigInsets.set(overrideConfigInsets.left, overrideConfigInsets.top,
+ overrideConfigInsets.right, overrideConfigInsets.bottom);
mNonDecorFrame.set(displayFrame);
mNonDecorFrame.inset(mNonDecorInsets);
mConfigFrame.set(displayFrame);
mConfigFrame.inset(mConfigInsets);
- mLegacyConfigFrame.set(displayFrame);
- mLegacyConfigFrame.inset(mLegacyConfigInsets);
+ mOverrideConfigFrame.set(displayFrame);
+ mOverrideConfigFrame.inset(mOverrideConfigInsets);
mNeedUpdate = false;
return insetsState;
}
@@ -1991,10 +1991,10 @@ public class DisplayPolicy {
void set(Info other) {
mNonDecorInsets.set(other.mNonDecorInsets);
mConfigInsets.set(other.mConfigInsets);
- mLegacyConfigInsets.set(other.mLegacyConfigInsets);
+ mOverrideConfigInsets.set(other.mOverrideConfigInsets);
mNonDecorFrame.set(other.mNonDecorFrame);
mConfigFrame.set(other.mConfigFrame);
- mLegacyConfigFrame.set(other.mLegacyConfigFrame);
+ mOverrideConfigFrame.set(other.mOverrideConfigFrame);
mNeedUpdate = false;
}
@@ -2107,7 +2107,7 @@ public class DisplayPolicy {
final InsetsState newInsetsState = newInfo.update(mDisplayContent, rotation, dw, dh);
final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh);
if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)
- && newInfo.mLegacyConfigFrame.equals(currentInfo.mLegacyConfigFrame)) {
+ && newInfo.mOverrideConfigFrame.equals(currentInfo.mOverrideConfigFrame)) {
// Even if the config frame is not changed in current rotation, it may change the
// insets in other rotations if the frame of insets source is changed.
final InsetsState currentInsetsState = mDisplayContent.mDisplayFrames.mInsetsState;
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index a8cbc621d966..88587666e321 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -519,8 +519,17 @@ class InsetsSourceProvider {
final SurfaceControl leash = mAdapter.mCapturedLeash;
mControlTarget = target;
updateVisibility();
+ boolean initiallyVisible = mClientVisible;
+ if (mSource.getType() == WindowInsets.Type.ime()) {
+ // The IME cannot be initially visible, see ControlAdapter#startAnimation below.
+ // Also, the ImeInsetsSourceConsumer clears the client visibility upon losing control,
+ // but this won't have reached here yet by the time the new control is created.
+ // Note: The DisplayImeController needs the correct previous client's visibility, so we
+ // only override the initiallyVisible here.
+ initiallyVisible = false;
+ }
mControl = new InsetsSourceControl(mSource.getId(), mSource.getType(), leash,
- mClientVisible, surfacePosition, getInsetsHint());
+ initiallyVisible, surfacePosition, getInsetsHint());
ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
"InsetsSource Control %s for target %s", mControl, mControlTarget);
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index b8bb258aa2ce..0ad601de95ec 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -61,6 +61,7 @@ import android.view.WindowManager;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
@@ -225,13 +226,16 @@ class KeyguardController {
if (keyguardShowing) {
state.mDismissalRequested = false;
}
- if (goingAwayRemoved) {
- // Keyguard dismiss is canceled. Send a transition to undo the changes and clean up
- // before holding the sleep token again.
+ if (goingAwayRemoved || (keyguardShowing && Flags.keyguardAppearTransition())) {
+ // Keyguard decided to show or stopped going away. Send a transition to animate back
+ // to the locked state before holding the sleep token again
final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
dc.requestTransitionAndLegacyPrepare(
TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
- mWindowManager.executeAppTransition();
+ if (Flags.keyguardAppearTransition()) {
+ dc.mWallpaperController.adjustWallpaperWindows();
+ }
+ dc.executeAppTransition();
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 3f245456d849..f220c9d06e14 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1084,6 +1084,10 @@ final class LetterboxUiController {
|| mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN);
}
+ boolean hasFullscreenOverride() {
+ return isSystemOverrideToFullscreenEnabled() || shouldApplyUserFullscreenOverride();
+ }
+
float getUserMinAspectRatio() {
switch (mUserAspectRatio) {
case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index e157318543f6..f8aa69b80253 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -912,7 +912,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
@Override
public void grantInputChannel(int displayId, SurfaceControl surface,
IBinder clientToken, @Nullable InputTransferToken hostInputTransferToken, int flags,
- int privateFlags, int type, int inputFeatures, IBinder windowToken,
+ int privateFlags, int inputFeatures, int type, IBinder windowToken,
InputTransferToken inputTransferToken, String inputHandleName,
InputChannel outInputChannel) {
if (hostInputTransferToken == null && !mCanAddInternalSystemWindow) {
@@ -925,7 +925,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
try {
mService.grantInputChannel(this, mUid, mPid, displayId, surface, clientToken,
hostInputTransferToken, flags, mCanAddInternalSystemWindow ? privateFlags : 0,
- type, inputFeatures, windowToken, inputTransferToken, inputHandleName,
+ inputFeatures, type, windowToken, inputTransferToken, inputHandleName,
outInputChannel);
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index bd1503f15666..218fb7f6b817 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2309,8 +2309,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
// area, i.e. the screen area without the system bars.
// The non decor inset are areas that could never be removed in Honeycomb. See
// {@link WindowManagerPolicy#getNonDecorInsetsLw}.
- calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
- false /* useLegacyInsetsForStableBounds */);
+ calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
} else {
// Apply the given non-decor and stable insets to calculate the corresponding bounds
// for screen size of configuration.
@@ -2408,11 +2407,9 @@ class TaskFragment extends WindowContainer<WindowContainer> {
* @param outNonDecorBounds where to place bounds with non-decor insets applied.
* @param outStableBounds where to place bounds with stable insets applied.
* @param bounds the bounds to inset.
- * @param useLegacyInsetsForStableBounds {@code true} if we need to use the legacy insets frame
- * for apps targeting U or before when calculating stable bounds.
*/
void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
- DisplayInfo displayInfo, boolean useLegacyInsetsForStableBounds) {
+ DisplayInfo displayInfo) {
outNonDecorBounds.set(bounds);
outStableBounds.set(bounds);
if (mDisplayContent == null) {
@@ -2424,11 +2421,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
final DisplayPolicy.DecorInsets.Info info = policy.getDecorInsetsInfo(
displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight);
intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
- if (!useLegacyInsetsForStableBounds) {
- intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
- } else {
- intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mLegacyConfigInsets);
- }
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
}
/**
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 222abc35ee0b..ce53290da49c 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -565,6 +565,9 @@ class TransitionController {
if (isTransientCollect(ar)) {
return true;
}
+ for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+ if (mWaitingTransitions.get(i).isTransientLaunch(ar)) return true;
+ }
for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
if (mPlayingTransitions.get(i).isTransientLaunch(ar)) return true;
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index c8cb934783ef..65e17615f775 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -856,19 +856,12 @@ class WallpaperController {
result.setWallpaperTarget(wallpaperTarget);
}
- public void updateWallpaperTokens(boolean keyguardLocked) {
- if (DEBUG_WALLPAPER) {
- Slog.v(TAG, "Wallpaper vis: target " + mWallpaperTarget + " prev="
- + mPrevWallpaperTarget);
- }
- updateWallpaperTokens(mWallpaperTarget != null || mPrevWallpaperTarget != null,
- keyguardLocked);
- }
-
/**
* Change the visibility of the top wallpaper to {@param visibility} and hide all the others.
*/
private void updateWallpaperTokens(boolean visibility, boolean keyguardLocked) {
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "updateWallpaperTokens requestedVisibility=%b on"
+ + " keyguardLocked=%b", visibility, keyguardLocked);
WindowState topWallpaper = mFindResults.getTopWallpaper(keyguardLocked);
WallpaperWindowToken topWallpaperToken =
topWallpaper == null ? null : topWallpaper.mToken.asWallpaperToken();
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 55eeaf22cca8..5c24eee63317 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -274,6 +274,12 @@ class WallpaperWindowToken extends WindowToken {
}
@Override
+ boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
+ // TODO(b/233286785): Support sync state for wallpaper. See WindowState#prepareSync.
+ return !mVisibleRequested || !hasVisibleNotDrawnWallpaper();
+ }
+
+ @Override
public String toString() {
if (stringName == null) {
StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2e721210d685..ed88b5a7c449 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -241,6 +241,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
+import android.util.IntArray;
import android.util.MergedConfiguration;
import android.util.Pair;
import android.util.Slog;
@@ -567,7 +568,7 @@ public class WindowManagerService extends IWindowManager.Stub
/** Device default insets types shall be excluded from config app sizes. */
final int mConfigTypes;
- final int mLegacyConfigTypes;
+ final int mOverrideConfigTypes;
final boolean mLimitedAlphaCompositing;
final int mMaxUiWidth;
@@ -1106,6 +1107,14 @@ public class WindowManagerService extends IWindowManager.Stub
@GuardedBy("mGlobalLock")
final SensitiveContentPackages mSensitiveContentPackages = new SensitiveContentPackages();
+ /**
+ * UIDs for which a Toast has been shown to indicate
+ * {@link LocalService#addBlockScreenCaptureForApps(ArraySet) screen capture blocking}. This is
+ * used to ensure we don't keep re-showing the Toast every time the window becomes visible.
+ * UIDs are removed when the app is removed from the block list.
+ */
+ @GuardedBy("mGlobalLock")
+ private final IntArray mCaptureBlockedToastShownUids = new IntArray();
/** Listener to notify activity manager about app transitions. */
final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
@@ -1238,20 +1247,18 @@ public class WindowManagerService extends IWindowManager.Stub
if (mFlags.mInsetsDecoupledConfiguration) {
mDecorTypes = 0;
mConfigTypes = 0;
- } else if (isScreenSizeDecoupledFromStatusBarAndCutout) {
- mDecorTypes = WindowInsets.Type.navigationBars();
- mConfigTypes = WindowInsets.Type.navigationBars();
} else {
mDecorTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars();
mConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
| WindowInsets.Type.navigationBars();
}
- if (isScreenSizeDecoupledFromStatusBarAndCutout) {
- // Do not fallback to legacy value for enabled devices.
- mLegacyConfigTypes = WindowInsets.Type.navigationBars();
+ if (isScreenSizeDecoupledFromStatusBarAndCutout && !mFlags.mInsetsDecoupledConfiguration) {
+ // If the global new behavior is not there, but the partial decouple flag is on.
+ mOverrideConfigTypes = 0;
} else {
- mLegacyConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
- | WindowInsets.Type.navigationBars();
+ mOverrideConfigTypes =
+ WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
+ | WindowInsets.Type.navigationBars();
}
mLetterboxConfiguration = new LetterboxConfiguration(
@@ -3685,12 +3692,6 @@ public class WindowManagerService extends IWindowManager.Stub
// Called by window manager policy. Not exposed externally.
@Override
- public void switchKeyboardLayout(int deviceId, int direction) {
- mInputManager.switchKeyboardLayout(deviceId, direction);
- }
-
- // Called by window manager policy. Not exposed externally.
- @Override
public void shutdown(boolean confirm) {
// Pass in the UI context, since ShutdownThread requires it (to show UI).
ShutdownThread.shutdown(ActivityThread.currentActivityThread().getSystemUiContext(),
@@ -8754,6 +8755,15 @@ public class WindowManagerService extends IWindowManager.Stub
if (modified) {
WindowManagerService.this.refreshScreenCaptureDisabled();
}
+ if (sensitiveContentImprovements()) {
+ for (int i = 0; i < packageInfos.size(); i++) {
+ int uid = packageInfos.valueAt(i).getUid();
+ if (mCaptureBlockedToastShownUids.contains(uid)) {
+ mCaptureBlockedToastShownUids.remove(
+ mCaptureBlockedToastShownUids.indexOf(uid));
+ }
+ }
+ }
}
}
@@ -8764,6 +8774,9 @@ public class WindowManagerService extends IWindowManager.Stub
if (modified) {
WindowManagerService.this.refreshScreenCaptureDisabled();
}
+ if (sensitiveContentImprovements()) {
+ mCaptureBlockedToastShownUids.clear();
+ }
}
}
@@ -10165,13 +10178,19 @@ public class WindowManagerService extends IWindowManager.Stub
* on sensitive content protections.
*/
private void showToastIfBlockingScreenCapture(@NonNull WindowState w) {
- // TODO(b/323580163): Check if already shown and update shown state.
- if (mSensitiveContentPackages.shouldBlockScreenCaptureForApp(w.getOwningPackage(),
- w.getOwningUid(), w.getWindowToken())) {
- Toast.makeText(mContext, Looper.getMainLooper(),
- mContext.getString(R.string.screen_not_shared_sensitive_content),
- Toast.LENGTH_SHORT)
- .show();
+ int uid = w.getOwningUid();
+ if (mCaptureBlockedToastShownUids.contains(uid)) {
+ return;
+ }
+ if (mSensitiveContentPackages.shouldBlockScreenCaptureForApp(w.getOwningPackage(), uid,
+ w.getWindowToken())) {
+ mCaptureBlockedToastShownUids.add(uid);
+ mH.post(() -> {
+ Toast.makeText(mContext, Looper.getMainLooper(),
+ mContext.getString(R.string.screen_not_shared_sensitive_content),
+ Toast.LENGTH_SHORT)
+ .show();
+ });
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 14ec41f072dd..e7088832500f 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1367,10 +1367,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final IBinder callerActivityToken = operation.getActivityToken();
final Intent activityIntent = operation.getActivityIntent();
final Bundle activityOptions = operation.getBundle();
- final int result = mService.getActivityStartController()
+ final int result = waitAsyncStart(() -> mService.getActivityStartController()
.startActivityInTaskFragment(taskFragment, activityIntent, activityOptions,
callerActivityToken, caller.mUid, caller.mPid,
- errorCallbackToken);
+ errorCallbackToken));
if (!isStartResultSuccessful(result)) {
sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
opType, convertStartFailureToThrowable(result, activityIntent));
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 6ac2774941e1..9d8246d95180 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -110,8 +110,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE;
private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
- private static final int MAX_RAPID_ACTIVITY_LAUNCH_COUNT = 500;
- private static final long RAPID_ACTIVITY_LAUNCH_MS = 300;
+ private static final int MAX_RAPID_ACTIVITY_LAUNCH_COUNT = 50;
+ private static final long RAPID_ACTIVITY_LAUNCH_MS = 500;
private static final long RESET_RAPID_ACTIVITY_LAUNCH_MS = 5 * RAPID_ACTIVITY_LAUNCH_MS;
public static final int STOPPED_STATE_NOT_STOPPED = 0;
@@ -202,6 +202,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// Whether this process has ever started a service with the BIND_INPUT_METHOD permission.
private volatile boolean mHasImeService;
+ /**
+ * Whether this process can use realtime prioirity (SCHED_FIFO) for its UI and render threads
+ * when this process is SCHED_GROUP_TOP_APP.
+ */
+ private final boolean mUseFifoUiScheduling;
+
/** Whether {@link #mActivities} is not empty. */
private volatile boolean mHasActivities;
/** All activities running in the process (exclude destroying). */
@@ -340,6 +346,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// TODO(b/151161907): Remove after support for display-independent (raw) SysUi configs.
mIsActivityConfigOverrideAllowed = false;
}
+ mUseFifoUiScheduling = com.android.window.flags.Flags.fifoPriorityForMajorUiProcesses()
+ && (isSysUiPackage || mAtm.isCallerRecents(uid));
mCanUseSystemGrammaticalGender = mAtm.mGrammaticalManagerInternal != null
&& mAtm.mGrammaticalManagerInternal.canGetSystemGrammaticalGender(mUid,
@@ -631,9 +639,15 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
if (mRapidActivityLaunchCount > MAX_RAPID_ACTIVITY_LAUNCH_COUNT) {
- Slog.w(TAG, "Killing " + mPid + " because of rapid activity launch");
- r.getRootTask().moveTaskToBack(r.getTask());
- mAtm.mH.post(() -> mAtm.mAmInternal.killProcess(mName, mUid, "rapidActivityLaunch"));
+ mRapidActivityLaunchCount = 0;
+ final Task task = r.getTask();
+ Slog.w(TAG, "Removing task " + task.mTaskId + " because of rapid activity launch");
+ mAtm.mH.post(() -> {
+ synchronized (mAtm.mGlobalLock) {
+ task.removeImmediately("rapid-activity-launch");
+ }
+ mAtm.mAmInternal.killProcess(mName, mUid, "rapidActivityLaunch");
+ });
}
}
@@ -1901,6 +1915,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
}
+ /** Returns {@code true} if the process prefers to use fifo scheduling. */
+ public boolean useFifoUiScheduling() {
+ return mUseFifoUiScheduling;
+ }
+
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
public void onTopProcChanged() {
if (mAtm.mVrController.isInterestingToSchedGroup()) {
@@ -2078,6 +2097,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
pw.println();
}
+ if (mUseFifoUiScheduling) {
+ pw.println(prefix + " mUseFifoUiScheduling=true");
+ }
final int stateFlags = mActivityStateFlags;
if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7a0245bc1acc..4d9fc6c14bc0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3701,22 +3701,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mDragResizingChangeReported = true;
mWindowFrames.clearReportResizeHints();
- // App window resize may trigger Activity#onConfigurationChanged, so we need to update
- // ActivityWindowInfo as well.
- final IBinder activityToken;
- final ActivityWindowInfo activityWindowInfo;
- if (mLastReportedActivityWindowInfo != null) {
- activityToken = mActivityRecord.token;
- activityWindowInfo = mLastReportedActivityWindowInfo;
- } else {
- activityToken = null;
- activityWindowInfo = null;
- }
-
final int prevRotation = mLastReportedConfiguration
.getMergedConfiguration().windowConfiguration.getRotation();
fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration,
- activityWindowInfo, true /* useLatestConfig */, false /* relayoutVisible */);
+ mLastReportedActivityWindowInfo, true /* useLatestConfig */,
+ false /* relayoutVisible */);
final boolean syncRedraw = shouldSendRedrawForSync();
final boolean syncWithBuffers = syncRedraw && shouldSyncWithBuffers();
final boolean reportDraw = syncRedraw || drawPending;
@@ -3740,14 +3729,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mLastReportedConfiguration, getCompatInsetsState(), forceRelayout,
alwaysConsumeSystemBars, displayId,
syncWithBuffers ? mSyncSeqId : -1, isDragResizing,
- activityToken, activityWindowInfo));
+ mLastReportedActivityWindowInfo));
onResizePostDispatched(drawPending, prevRotation, displayId);
} else {
// TODO(b/301870955): cleanup after launch
try {
mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
- syncWithBuffers ? mSyncSeqId : -1, isDragResizing, activityWindowInfo);
+ syncWithBuffers ? mSyncSeqId : -1, isDragResizing,
+ mLastReportedActivityWindowInfo);
onResizePostDispatched(drawPending, prevRotation, displayId);
} catch (RemoteException e) {
// Cancel orientation change of this window to avoid blocking unfreeze display.
diff --git a/services/core/jni/com_android_server_am_OomConnection.cpp b/services/core/jni/com_android_server_am_OomConnection.cpp
index 054937fc683e..4d07776d8023 100644
--- a/services/core/jni/com_android_server_am_OomConnection.cpp
+++ b/services/core/jni/com_android_server_am_OomConnection.cpp
@@ -92,9 +92,11 @@ static jobjectArray android_server_am_OomConnection_waitOom(JNIEnv* env, jobject
memevent_listener.deregisterAllEvents();
jniThrowRuntimeException(env, "Failed creating java string for process name");
}
- jobject java_oom_kill = env->NewObject(sOomKillRecordInfo.clazz, sOomKillRecordInfo.ctor,
- oom_kill.timestamp_ms, oom_kill.pid, oom_kill.uid,
- process_name, oom_kill.oom_score_adj);
+ jobject java_oom_kill =
+ env->NewObject(sOomKillRecordInfo.clazz, sOomKillRecordInfo.ctor,
+ oom_kill.timestamp_ms, oom_kill.pid, oom_kill.uid, process_name,
+ oom_kill.oom_score_adj, oom_kill.total_vm_kb, oom_kill.anon_rss_kb,
+ oom_kill.file_rss_kb, oom_kill.shmem_rss_kb, oom_kill.pgtables_kb);
if (java_oom_kill == NULL) {
memevent_listener.deregisterAllEvents();
jniThrowRuntimeException(env, "Failed to create OomKillRecord object");
@@ -115,8 +117,8 @@ int register_android_server_am_OomConnection(JNIEnv* env) {
sOomKillRecordInfo.clazz = FindClassOrDie(env, "android/os/OomKillRecord");
sOomKillRecordInfo.clazz = MakeGlobalRefOrDie(env, sOomKillRecordInfo.clazz);
- sOomKillRecordInfo.ctor =
- GetMethodIDOrDie(env, sOomKillRecordInfo.clazz, "<init>", "(JIILjava/lang/String;S)V");
+ sOomKillRecordInfo.ctor = GetMethodIDOrDie(env, sOomKillRecordInfo.clazz, "<init>",
+ "(JIILjava/lang/String;SJJJJJ)V");
return RegisterMethodsOrDie(env, "com/android/server/am/OomConnection", sOomConnectionMethods,
NELEM(sOomConnectionMethods));
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 859802354e04..6143f1dd5b1c 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -176,7 +176,9 @@
<xs:element type="idleScreenRefreshRateTimeout" name="idleScreenRefreshRateTimeout" minOccurs="0">
<xs:annotation name="final"/>
</xs:element>
-
+ <xs:element name="supportsVrr" type="xs:boolean" minOccurs="0">
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
@@ -226,11 +228,8 @@
<xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1"
maxOccurs="1">
</xs:element>
- <xs:element name="nits" type="xs:float" maxOccurs="unbounded">
- </xs:element>
- <xs:element name="backlight" type="xs:float" maxOccurs="unbounded">
- </xs:element>
- <xs:element name="brightness" type="xs:float" maxOccurs="unbounded">
+ <!-- Mapping of nits -> backlight -> brightness -->
+ <xs:element name="brightnessMapping" type="comprehensiveBrightnessMap" maxOccurs="1">
</xs:element>
<!-- Mapping of current lux to minimum allowed nits values. -->
<xs:element name="luxToMinimumNitsMap" type="nitsMap" maxOccurs="1">
@@ -449,6 +448,35 @@
</xs:sequence>
</xs:complexType>
+ <xs:complexType name="comprehensiveBrightnessMap">
+ <xs:sequence>
+ <xs:element name="brightnessPoint" type="brightnessPoint" maxOccurs="unbounded" minOccurs="2">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ <!-- valid value of interpolation if specified: linear -->
+ <xs:attribute name="interpolation" type="xs:string" use="optional"/>
+ </xs:complexType>
+
+ <xs:complexType name="brightnessPoint">
+ <xs:sequence>
+ <xs:element type="nonNegativeDecimal" name="nits">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element type="nonNegativeDecimal" name="backlight">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element type="nonNegativeDecimal" name="brightness">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+
<xs:complexType name="sdrHdrRatioMap">
<xs:sequence>
<xs:element name="point" type="sdrHdrRatioPoint" maxOccurs="unbounded" minOccurs="2">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 4ce4cc3186e0..45ec8f250efa 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -53,6 +53,16 @@ package com.android.server.display.config {
method public final void setType(@NonNull com.android.server.display.config.PredefinedBrightnessLimitNames);
}
+ public class BrightnessPoint {
+ ctor public BrightnessPoint();
+ method @NonNull public final java.math.BigDecimal getBacklight();
+ method @NonNull public final java.math.BigDecimal getBrightness();
+ method @NonNull public final java.math.BigDecimal getNits();
+ method public final void setBacklight(@NonNull java.math.BigDecimal);
+ method public final void setBrightness(@NonNull java.math.BigDecimal);
+ method public final void setNits(@NonNull java.math.BigDecimal);
+ }
+
public class BrightnessThresholds {
ctor public BrightnessThresholds();
method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints();
@@ -76,6 +86,13 @@ package com.android.server.display.config {
method public final void setThermalStatus(@NonNull com.android.server.display.config.ThermalStatus);
}
+ public class ComprehensiveBrightnessMap {
+ ctor public ComprehensiveBrightnessMap();
+ method @NonNull public final java.util.List<com.android.server.display.config.BrightnessPoint> getBrightnessPoint();
+ method public String getInterpolation();
+ method public void setInterpolation(String);
+ }
+
public class Density {
ctor public Density();
method @NonNull public final java.math.BigInteger getDensity();
@@ -135,6 +152,7 @@ package com.android.server.display.config {
method public final java.math.BigDecimal getScreenBrightnessRampSlowIncreaseIdle();
method public final com.android.server.display.config.SensorDetails getScreenOffBrightnessSensor();
method public final com.android.server.display.config.IntegerArray getScreenOffBrightnessSensorValueToLux();
+ method public final boolean getSupportsVrr();
method public final com.android.server.display.config.SensorDetails getTempSensor();
method @NonNull public final com.android.server.display.config.ThermalThrottling getThermalThrottling();
method public final com.android.server.display.config.UsiVersion getUsiVersion();
@@ -171,6 +189,7 @@ package com.android.server.display.config {
method public final void setScreenBrightnessRampSlowIncreaseIdle(java.math.BigDecimal);
method public final void setScreenOffBrightnessSensor(com.android.server.display.config.SensorDetails);
method public final void setScreenOffBrightnessSensorValueToLux(com.android.server.display.config.IntegerArray);
+ method public final void setSupportsVrr(boolean);
method public final void setTempSensor(com.android.server.display.config.SensorDetails);
method public final void setThermalThrottling(@NonNull com.android.server.display.config.ThermalThrottling);
method public final void setUsiVersion(com.android.server.display.config.UsiVersion);
@@ -183,12 +202,11 @@ package com.android.server.display.config {
public class EvenDimmerMode {
ctor public EvenDimmerMode();
- method public java.util.List<java.lang.Float> getBacklight();
- method public java.util.List<java.lang.Float> getBrightness();
+ method public com.android.server.display.config.ComprehensiveBrightnessMap getBrightnessMapping();
method public boolean getEnabled();
method public com.android.server.display.config.NitsMap getLuxToMinimumNitsMap();
- method public java.util.List<java.lang.Float> getNits();
method public java.math.BigDecimal getTransitionPoint();
+ method public void setBrightnessMapping(com.android.server.display.config.ComprehensiveBrightnessMap);
method public void setEnabled(boolean);
method public void setLuxToMinimumNitsMap(com.android.server.display.config.NitsMap);
method public void setTransitionPoint(java.math.BigDecimal);
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index f5ba50d7f079..bb46c44c56c4 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -21,6 +21,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.credentials.ClearCredentialStateException;
import android.credentials.ClearCredentialStateRequest;
+import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.selection.ProviderData;
@@ -41,7 +42,7 @@ import java.util.Set;
public final class ClearRequestSession extends RequestSession<ClearCredentialStateRequest,
IClearCredentialStateCallback, Void>
implements ProviderSession.ProviderInternalCallback<Void> {
- private static final String TAG = "GetRequestSession";
+ private static final String TAG = CredentialManager.TAG;
public ClearRequestSession(Context context, RequestSession.SessionLifetime sessionCallback,
Object lock, int userId, int callingUid,
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index cac42b17553a..3513cb54c9bd 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -50,7 +50,7 @@ import java.util.Set;
public final class CreateRequestSession extends RequestSession<CreateCredentialRequest,
ICreateCredentialCallback, CreateCredentialResponse>
implements ProviderSession.ProviderInternalCallback<CreateCredentialResponse> {
- private static final String TAG = "CreateRequestSession";
+ private static final String TAG = CredentialManager.TAG;
private final Set<ComponentName> mPrimaryProviders;
CreateRequestSession(@NonNull Context context, RequestSession.SessionLifetime sessionCallback,
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 281fb1c4635b..6ef14366b9e7 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -34,6 +34,7 @@ import android.content.pm.PackageManager;
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialRequest;
+import android.credentials.CredentialManager;
import android.credentials.CredentialOption;
import android.credentials.CredentialProviderInfo;
import android.credentials.GetCandidateCredentialsException;
@@ -92,7 +93,7 @@ public final class CredentialManagerService
extends AbstractMasterSystemService<
CredentialManagerService, CredentialManagerServiceImpl> {
- private static final String TAG = "CredManSysService";
+ private static final String TAG = CredentialManager.TAG;
private static final String PERMISSION_DENIED_ERROR = "permission_denied";
private static final String PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR =
"Caller is missing WRITE_SECURE_SETTINGS permission";
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index 379800b31597..38ad5b6594b5 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.service.credentials.CredentialProviderInfoFactory;
import android.util.Slog;
@@ -36,7 +37,7 @@ import java.util.List;
*/
public final class CredentialManagerServiceImpl extends
AbstractPerUserSystemService<CredentialManagerServiceImpl, CredentialManagerService> {
- private static final String TAG = "CredManSysServiceImpl";
+ private static final String TAG = CredentialManager.TAG;
@GuardedBy("mLock")
@NonNull
@@ -93,7 +94,10 @@ public final class CredentialManagerServiceImpl extends
public ProviderSession initiateProviderSessionForRequestLocked(
RequestSession requestSession, List<String> requestOptions) {
if (!requestOptions.isEmpty() && !isServiceCapableLocked(requestOptions)) {
- Slog.i(TAG, "Service does not have the required capabilities");
+ if (mInfo != null) {
+ Slog.i(TAG, "Service does not have the required capabilities: "
+ + mInfo.getComponentName());
+ }
return null;
}
if (mInfo == null) {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 24f66977ee90..bfa2d6138f04 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -48,7 +48,6 @@ import java.util.UUID;
/** Initiates the Credential Manager UI and receives results. */
public class CredentialManagerUi {
- private static final String TAG = "CredentialManagerUi";
@NonNull
private final CredentialManagerUiCallback mCallbacks;
@NonNull
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index fd2a9a20640b..69d32a0f8825 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -22,6 +22,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.credentials.Constants;
+import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.credentials.GetCandidateCredentialsException;
import android.credentials.GetCandidateCredentialsResponse;
@@ -54,7 +55,7 @@ import java.util.Set;
public class GetCandidateRequestSession extends RequestSession<GetCredentialRequest,
IGetCandidateCredentialsCallback, GetCandidateCredentialsResponse>
implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
- private static final String TAG = "GetCandidateRequestSession";
+ private static final String TAG = CredentialManager.TAG;
private static final String SESSION_ID_KEY = "autofill_session_id";
private static final String REQUEST_ID_KEY = "autofill_request_id";
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index d55d8effd381..c26229b75300 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -20,6 +20,7 @@ import android.Manifest;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.credentials.CredentialManager;
import android.credentials.CredentialOption;
import android.credentials.CredentialProviderInfo;
import android.credentials.GetCredentialException;
@@ -48,7 +49,7 @@ import java.util.Set;
public class GetRequestSession extends RequestSession<GetCredentialRequest,
IGetCredentialCallback, GetCredentialResponse>
implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
- private static final String TAG = "GetRequestSession";
+ private static final String TAG = CredentialManager.TAG;
public GetRequestSession(Context context, RequestSession.SessionLifetime sessionCallback,
Object lock, int userId, int callingUid,
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 16bf17781eea..ac4aac694c3a 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -20,6 +20,7 @@ import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.credentials.CredentialManager;
import android.util.Slog;
import com.android.internal.util.FrameworkStatsLog;
@@ -44,7 +45,7 @@ import java.util.Map;
public class MetricUtilities {
private static final boolean LOG_FLAG = true;
- private static final String TAG = "MetricUtilities";
+ private static final String TAG = CredentialManager.TAG;
public static final String USER_CANCELED_SUBSTRING = "TYPE_USER_CANCELED";
public static final int MIN_EMIT_WAIT_TIME_MS = 10;
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index e4b5c776301e..f6b107b60d62 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
+import android.credentials.CredentialManager;
import android.credentials.CredentialOption;
import android.credentials.GetCredentialRequest;
import android.credentials.IGetCredentialCallback;
@@ -44,7 +45,7 @@ import java.util.stream.Collectors;
* responses from providers, and the UX app, and updates the provider(s) state.
*/
public class PrepareGetRequestSession extends GetRequestSession {
- private static final String TAG = "PrepareGetRequestSession";
+ private static final String TAG = CredentialManager.TAG;
private final IPrepareGetCredentialCallback mPrepareGetCredentialCallback;
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 6a1b1db756b5..6759dbb6f534 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -20,6 +20,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.credentials.ClearCredentialStateException;
+import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.credentials.selection.ProviderData;
import android.credentials.selection.ProviderPendingIntentResponse;
@@ -37,7 +38,7 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS
Void>
implements
RemoteCredentialService.ProviderCallbacks<Void> {
- private static final String TAG = "ProviderClearSession";
+ private static final String TAG = CredentialManager.TAG;
private ClearCredentialStateException mProviderException;
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 6361aeb4f0d7..bee7f6c9e908 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.Intent;
import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialResponse;
+import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.credentials.selection.CreateCredentialProviderData;
import android.credentials.selection.Entry;
@@ -51,7 +52,7 @@ import java.util.Map;
*/
public final class ProviderCreateSession extends ProviderSession<
BeginCreateCredentialRequest, BeginCreateCredentialResponse> {
- private static final String TAG = "ProviderCreateSession";
+ private static final String TAG = CredentialManager.TAG;
// Key to be used as an entry key for a save entry
public static final String SAVE_ENTRY_KEY = "save_entry_key";
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index c5f292118030..e18ef2b3230d 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -22,6 +22,7 @@ import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.credentials.CredentialManager;
import android.credentials.CredentialOption;
import android.credentials.CredentialProviderInfo;
import android.credentials.GetCredentialException;
@@ -61,7 +62,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
BeginGetCredentialResponse>
implements
RemoteCredentialService.ProviderCallbacks<BeginGetCredentialResponse> {
- private static final String TAG = "ProviderGetSession";
+ private static final String TAG = CredentialManager.TAG;
// Key to be used as the entry key for an action entry
public static final String ACTION_ENTRY_KEY = "action_key";
// Key to be used as the entry key for the authentication entry
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index f162916690fd..83f9c24cdaf4 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -22,6 +22,7 @@ import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.credentials.CredentialManager;
import android.credentials.CredentialOption;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialResponse;
@@ -58,7 +59,7 @@ import java.util.stream.Stream;
public class ProviderRegistryGetSession extends ProviderSession<CredentialOption,
Set<CredentialDescriptionRegistry.FilterResult>> {
- private static final String TAG = "ProviderRegistryGetSession";
+ private static final String TAG = CredentialManager.TAG;
@VisibleForTesting
static final String CREDENTIAL_ENTRY_KEY = "credential_key";
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index dfc08f04386e..8f0ae9058814 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.credentials.Credential;
+import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.credentials.selection.ProviderData;
import android.credentials.selection.ProviderPendingIntentResponse;
@@ -44,7 +45,7 @@ import java.util.UUID;
public abstract class ProviderSession<T, R>
implements RemoteCredentialService.ProviderCallbacks<R> {
- private static final String TAG = "ProviderSession";
+ private static final String TAG = CredentialManager.TAG;
@NonNull
protected final Context mContext;
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index 4bcf8be0d21e..c36140685e03 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.Intent;
import android.credentials.ClearCredentialStateException;
import android.credentials.CreateCredentialException;
+import android.credentials.CredentialManager;
import android.credentials.GetCredentialException;
import android.os.Binder;
import android.os.Handler;
@@ -58,7 +59,7 @@ import java.util.concurrent.atomic.AtomicReference;
*/
public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialProviderService> {
- private static final String TAG = "RemoteCredentialService";
+ private static final String TAG = CredentialManager.TAG;
/** Timeout for a single request. */
private static final long TIMEOUT_REQUEST_MILLIS = 3 * DateUtils.SECOND_IN_MILLIS;
/** Timeout to unbind after the task queue is empty. */
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index a5b9aa68b22e..054ba2bca71b 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -23,6 +23,7 @@ import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.credentials.flags.Flags;
import android.credentials.selection.ProviderData;
@@ -56,7 +57,7 @@ import java.util.concurrent.ConcurrentHashMap;
* every time a new response type is expected from the providers.
*/
abstract class RequestSession<T, U, V> implements CredentialManagerUi.CredentialManagerUiCallback {
- private static final String TAG = "RequestSession";
+ private static final String TAG = CredentialManager.TAG;
public interface SessionLifetime {
/** Called when the user makes a selection. */
diff --git a/services/devicepolicy/TEST_MAPPING b/services/devicepolicy/TEST_MAPPING
index 0d5534ba74df..b8cb4a91d6f1 100644
--- a/services/devicepolicy/TEST_MAPPING
+++ b/services/devicepolicy/TEST_MAPPING
@@ -26,5 +26,12 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ // TODO(b/332974906): Promote in presubmit presubmit-devicepolicy.
+ "name": "CtsDevicePolicyManagerTestCases_NoFlakes_NoLarge",
+ "name": "CtsDevicePolicyManagerTestCases_ParentProfileApiDisabled"
+ }
]
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index f39d0193f28a..065c14e3f208 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -103,7 +103,7 @@ final class DevicePolicyEngine {
UserManager.DISALLOW_CELLULAR_2G);
//TODO(b/295504706) : Speak to security team to decide what to set Policy_Size_Limit
- private static final int DEFAULT_POLICY_SIZE_LIMIT = -1;
+ static final int DEFAULT_POLICY_SIZE_LIMIT = -1;
private final Context mContext;
private final UserManager mUserManager;
@@ -225,7 +225,7 @@ final class DevicePolicyEngine {
synchronized (mLock) {
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value,
policyDefinition, userId)) {
return;
@@ -350,7 +350,7 @@ final class DevicePolicyEngine {
}
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
}
@@ -496,7 +496,7 @@ final class DevicePolicyEngine {
synchronized (mLock) {
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value,
policyDefinition, UserHandle.USER_ALL)) {
return;
@@ -568,7 +568,7 @@ final class DevicePolicyEngine {
synchronized (mLock) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
decreasePolicySizeForAdmin(policyState, enforcingAdmin);
}
@@ -1598,6 +1598,7 @@ final class DevicePolicyEngine {
existingPolicySize = sizeOf(policyState.getPoliciesSetByAdmins().get(admin));
}
int policySize = sizeOf(value);
+
// Policy size limit is disabled if mPolicySizeLimit is -1.
if (mPolicySizeLimit == -1
|| currentAdminPoliciesSize + policySize - existingPolicySize < mPolicySizeLimit) {
@@ -1657,10 +1658,6 @@ final class DevicePolicyEngine {
* the limitation.
*/
void setMaxPolicyStorageLimit(int storageLimit) {
- if (storageLimit < DEFAULT_POLICY_SIZE_LIMIT && storageLimit != -1) {
- throw new IllegalArgumentException("Can't set a size limit less than the minimum "
- + "allowed size.");
- }
mPolicySizeLimit = storageLimit;
}
@@ -1672,6 +1669,15 @@ final class DevicePolicyEngine {
return mPolicySizeLimit;
}
+ int getPolicySizeForAdmin(EnforcingAdmin admin) {
+ if (mAdminPolicySize.contains(admin.getUserId())
+ && mAdminPolicySize.get(
+ admin.getUserId()).containsKey(admin)) {
+ return mAdminPolicySize.get(admin.getUserId()).get(admin);
+ }
+ return 0;
+ }
+
public void dump(IndentingPrintWriter pw) {
synchronized (mLock) {
pw.println("Local Policies: ");
@@ -1906,7 +1912,7 @@ final class DevicePolicyEngine {
private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer)
throws IOException {
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
if (mAdminPolicySize != null) {
for (int i = 0; i < mAdminPolicySize.size(); i++) {
int userId = mAdminPolicySize.keyAt(i);
@@ -1930,7 +1936,7 @@ final class DevicePolicyEngine {
private void writeMaxPolicySizeInner(TypedXmlSerializer serializer)
throws IOException {
- if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
return;
}
serializer.startTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT);
@@ -2095,7 +2101,7 @@ final class DevicePolicyEngine {
private void readMaxPolicySizeInner(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
- if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
return;
}
mPolicySizeLimit = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3dd7b5480da1..cb637579d8db 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -88,6 +88,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WALLPAPER;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WINDOWS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIPE_DATA;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT;
import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
import static android.Manifest.permission.MASTER_CLEAR;
import static android.Manifest.permission.NOTIFY_PENDING_SYSTEM_UPDATE;
@@ -268,6 +269,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOM
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
+import static com.android.server.devicepolicy.DevicePolicyEngine.DEFAULT_POLICY_SIZE_LIMIT;
import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER;
import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -742,7 +744,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* These cannot be set on the managed profile's parent DPM instance
*/
private static final int PROFILE_KEYGUARD_FEATURES_PROFILE_ONLY =
- DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+ DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS
+ | DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL;
/** Keyguard features that are allowed to be set on a managed profile */
private static final int PROFILE_KEYGUARD_FEATURES =
@@ -2251,11 +2254,41 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (userHandle == UserHandle.USER_SYSTEM) {
mStateCache.setDeviceProvisioned(policy.mUserSetupComplete);
}
+ if (Flags.headlessSingleUserBadDeviceAdminStateFix()) {
+ fixBadDeviceAdminStateForInternalUsers(userHandle, policy);
+ }
}
return policy;
}
}
+ private void fixBadDeviceAdminStateForInternalUsers(int userId, DevicePolicyData policy) {
+ ComponentName component = mOwners.getDeviceOwnerComponent();
+ int doUserId = mOwners.getDeviceOwnerUserId();
+ ComponentName cloudDpc = new ComponentName(
+ "com.google.android.apps.work.clouddpc",
+ "com.google.android.apps.work.clouddpc.receivers.CloudDeviceAdminReceiver");
+ if (component == null || doUserId != userId || !component.equals(cloudDpc)) {
+ return;
+ }
+ Slogf.i(LOG_TAG, "Attempting to apply a temp fix for cloudpc internal users' bad state.");
+ final int n = policy.mAdminList.size();
+ for (int i = 0; i < n; i++) {
+ ActiveAdmin admin = policy.mAdminList.get(i);
+ if (component.equals(admin.info.getComponent())) {
+ Slogf.i(LOG_TAG, "An ActiveAdmin already exists, fix not required.");
+ return;
+ }
+ }
+ DeviceAdminInfo dai = findAdmin(component, userId, /* throwForMissingPermission= */ false);
+ if (dai != null) {
+ ActiveAdmin ap = new ActiveAdmin(dai, /* parent */ false);
+ policy.mAdminMap.put(ap.info.getComponent(), ap);
+ policy.mAdminList.add(ap);
+ Slogf.i(LOG_TAG, "Fix applied, an ActiveAdmin has been added.");
+ }
+ }
+
/**
* Creates and loads the policy data from xml for data that is shared between
* various profiles of a user. In contrast to {@link #getUserData(int)}
@@ -12138,7 +12171,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
if (packageList != null) {
- if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
for (String pkg : packageList) {
PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
}
@@ -12875,7 +12908,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Slogf.i(LOG_TAG, "Stopping user %d", userId);
final long id = mInjector.binderClearCallingIdentity();
try {
- switch (mInjector.getIActivityManager().stopUser(userId, true /*force*/, null)) {
+ switch (mInjector.getIActivityManager().stopUserWithCallback(userId, null)) {
case ActivityManager.USER_OP_SUCCESS:
return UserManager.USER_OPERATION_SUCCESS;
case ActivityManager.USER_OP_IS_CURRENT:
@@ -13913,7 +13946,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return;
}
- if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
PolicySizeVerifier.enforceMaxStringLength(accountType, "account type");
}
@@ -14527,7 +14560,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
public void setLockTaskPackages(ComponentName who, String callerPackageName, String[] packages)
throws SecurityException {
Objects.requireNonNull(packages, "packages is null");
- if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
for (String pkg : packages) {
PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
}
@@ -24536,19 +24569,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setMaxPolicyStorageLimit(String callerPackageName, int storageLimit) {
- if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
return;
}
CallerIdentity caller = getCallerIdentity(callerPackageName);
enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(),
caller.getUserId());
+ if (storageLimit < DEFAULT_POLICY_SIZE_LIMIT && storageLimit != -1) {
+ throw new IllegalArgumentException("Can't set a size limit less than the minimum "
+ + "allowed size.");
+ }
mDevicePolicyEngine.setMaxPolicyStorageLimit(storageLimit);
}
@Override
public int getMaxPolicyStorageLimit(String callerPackageName) {
- if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
+ if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
return -1;
}
CallerIdentity caller = getCallerIdentity(callerPackageName);
@@ -24559,6 +24596,32 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
@Override
+ public void forceSetMaxPolicyStorageLimit(String callerPackageName, int storageLimit) {
+ if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
+ return;
+ }
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ enforcePermission(MANAGE_DEVICE_POLICY_STORAGE_LIMIT, caller.getPackageName(),
+ caller.getUserId());
+
+ mDevicePolicyEngine.setMaxPolicyStorageLimit(storageLimit);
+ }
+
+ @Override
+ public int getPolicySizeForAdmin(
+ String callerPackageName, android.app.admin.EnforcingAdmin admin) {
+ if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
+ return -1;
+ }
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ enforcePermission(MANAGE_DEVICE_POLICY_STORAGE_LIMIT, caller.getPackageName(),
+ caller.getUserId());
+
+ return mDevicePolicyEngine.getPolicySizeForAdmin(
+ EnforcingAdmin.createEnforcingAdmin(admin));
+ }
+
+ @Override
public int getHeadlessDeviceOwnerMode(String callerPackageName) {
final CallerIdentity caller = getCallerIdentity(callerPackageName);
enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(),
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index d234dee3c8f7..02590f97ab6b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.app.admin.Authority;
import android.app.admin.DeviceAdminAuthority;
import android.app.admin.DpcAuthority;
+import android.app.admin.PackagePermissionPolicyKey;
import android.app.admin.RoleAuthority;
import android.app.admin.UnknownAuthority;
import android.content.ComponentName;
@@ -105,6 +106,32 @@ final class EnforcingAdmin {
userId, activeAdmin);
}
+ static EnforcingAdmin createEnforcingAdmin(android.app.admin.EnforcingAdmin admin) {
+ Objects.requireNonNull(admin);
+ Authority authority = admin.getAuthority();
+ Set<String> internalAuthorities = new HashSet<>();
+ if (DpcAuthority.DPC_AUTHORITY.equals(authority)) {
+ return new EnforcingAdmin(
+ admin.getPackageName(), admin.getComponentName(),
+ Set.of(DPC_AUTHORITY), admin.getUserHandle().getIdentifier(),
+ /* activeAdmin = */ null);
+ } else if (DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY.equals(authority)) {
+ return new EnforcingAdmin(
+ admin.getPackageName(), admin.getComponentName(),
+ Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier(),
+ /* activeAdmin = */ null);
+ } else if (authority instanceof RoleAuthority roleAuthority) {
+ return new EnforcingAdmin(
+ admin.getPackageName(), admin.getComponentName(),
+ Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier(),
+ /* activeAdmin = */ null,
+ /* isRoleAuthority = */ true);
+ }
+ return new EnforcingAdmin(admin.getPackageName(), admin.getComponentName(),
+ Set.of(), admin.getUserHandle().getIdentifier(),
+ /* activeAdmin = */ null);
+ }
+
static String getRoleAuthorityOf(String roleName) {
return ROLE_AUTHORITY_PREFIX + roleName;
}
@@ -154,6 +181,20 @@ final class EnforcingAdmin {
mActiveAdmin = activeAdmin;
}
+ private EnforcingAdmin(
+ String packageName, @Nullable ComponentName componentName, Set<String> authorities,
+ int userId, @Nullable ActiveAdmin activeAdmin, boolean isRoleAuthority) {
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(authorities);
+
+ mIsRoleAuthority = isRoleAuthority;
+ mPackageName = packageName;
+ mComponentName = componentName;
+ mAuthorities = new HashSet<>(authorities);
+ mUserId = userId;
+ mActiveAdmin = activeAdmin;
+ }
+
private static Set<String> getRoleAuthoritiesOrDefault(String packageName, int userId) {
Set<String> roles = getRoles(packageName, userId);
Set<String> authorities = new HashSet<>();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0a7f49da0c31..3c6b500d9ced 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -108,29 +108,50 @@ import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accounts.AccountManagerService;
import com.android.server.adaptiveauth.AdaptiveAuthService;
+import com.android.server.adb.AdbService;
+import com.android.server.alarm.AlarmManagerService;
import com.android.server.am.ActivityManagerService;
+import com.android.server.ambientcontext.AmbientContextManagerService;
+import com.android.server.app.GameManagerService;
import com.android.server.appbinding.AppBindingService;
+import com.android.server.apphibernation.AppHibernationService;
import com.android.server.appop.AppOpMigrationHelper;
import com.android.server.appop.AppOpMigrationHelperImpl;
+import com.android.server.appprediction.AppPredictionManagerService;
+import com.android.server.appwidget.AppWidgetService;
import com.android.server.art.ArtModuleServiceInitializer;
import com.android.server.art.DexUseManagerLocal;
import com.android.server.attention.AttentionManagerService;
import com.android.server.audio.AudioService;
+import com.android.server.autofill.AutofillManagerService;
+import com.android.server.backup.BackupManagerService;
import com.android.server.biometrics.AuthService;
import com.android.server.biometrics.BiometricService;
import com.android.server.biometrics.sensors.face.FaceService;
import com.android.server.biometrics.sensors.fingerprint.FingerprintService;
import com.android.server.biometrics.sensors.iris.IrisService;
+import com.android.server.blob.BlobStoreManagerService;
import com.android.server.broadcastradio.BroadcastRadioService;
import com.android.server.camera.CameraServiceProxy;
import com.android.server.clipboard.ClipboardService;
+import com.android.server.companion.CompanionDeviceManagerService;
+import com.android.server.companion.virtual.VirtualDeviceManagerService;
import com.android.server.compat.PlatformCompat;
import com.android.server.compat.PlatformCompatNative;
+import com.android.server.compat.overrides.AppCompatOverridesService;
+import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.PacProxyService;
+import com.android.server.content.ContentService;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
+import com.android.server.contentcapture.ContentCaptureManagerService;
+import com.android.server.contentsuggestions.ContentSuggestionsManagerService;
+import com.android.server.contextualsearch.ContextualSearchManagerService;
import com.android.server.coverage.CoverageService;
import com.android.server.cpu.CpuMonitorService;
+import com.android.server.credentials.CredentialManagerService;
import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.devicepolicy.DevicePolicyManagerService;
import com.android.server.devicestate.DeviceStateManagerService;
@@ -147,14 +168,20 @@ import com.android.server.incident.IncidentCompanionService;
import com.android.server.input.InputManagerService;
import com.android.server.inputmethod.InputMethodManagerService;
import com.android.server.integrity.AppIntegrityManagerService;
+import com.android.server.job.JobSchedulerService;
import com.android.server.lights.LightsService;
import com.android.server.locales.LocaleManagerService;
import com.android.server.location.LocationManagerService;
import com.android.server.location.altitude.AltitudeService;
+import com.android.server.locksettings.LockSettingsService;
import com.android.server.logcat.LogcatManagerService;
+import com.android.server.media.MediaResourceMonitorService;
import com.android.server.media.MediaRouterService;
+import com.android.server.media.MediaSessionService;
import com.android.server.media.metrics.MediaMetricsManagerService;
import com.android.server.media.projection.MediaProjectionManagerService;
+import com.android.server.midi.MidiService;
+import com.android.server.musicrecognition.MusicRecognitionManagerService;
import com.android.server.net.NetworkManagementService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.watchlist.NetworkWatchlistService;
@@ -195,12 +222,16 @@ import com.android.server.power.ShutdownThread;
import com.android.server.power.ThermalManagerService;
import com.android.server.power.hint.HintManagerService;
import com.android.server.powerstats.PowerStatsService;
+import com.android.server.print.PrintManagerService;
import com.android.server.profcollect.ProfcollectForwardingService;
import com.android.server.recoverysystem.RecoverySystemService;
import com.android.server.resources.ResourcesManagerService;
import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.role.RoleServicePlatformHelper;
+import com.android.server.rollback.RollbackManagerService;
import com.android.server.rotationresolver.RotationResolverManagerService;
+import com.android.server.search.SearchManagerService;
+import com.android.server.searchui.SearchUiManagerService;
import com.android.server.security.AttestationVerificationManagerService;
import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
@@ -210,16 +241,28 @@ import com.android.server.selinux.SelinuxAuditLogsService;
import com.android.server.sensorprivacy.SensorPrivacyService;
import com.android.server.sensors.SensorService;
import com.android.server.signedconfig.SignedConfigService;
+import com.android.server.slice.SliceManagerService;
+import com.android.server.smartspace.SmartspaceManagerService;
import com.android.server.soundtrigger.SoundTriggerService;
import com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService;
+import com.android.server.speech.SpeechRecognitionManagerService;
+import com.android.server.stats.bootstrap.StatsBootstrapAtomService;
+import com.android.server.stats.pull.StatsPullAtomService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
+import com.android.server.systemcaptions.SystemCaptionsManagerService;
import com.android.server.telecom.TelecomLoaderService;
import com.android.server.testharness.TestHarnessModeService;
import com.android.server.textclassifier.TextClassificationManagerService;
import com.android.server.textservices.TextServicesManagerService;
+import com.android.server.texttospeech.TextToSpeechManagerService;
+import com.android.server.timedetector.GnssTimeUpdateService;
import com.android.server.timedetector.NetworkTimeUpdateService;
+import com.android.server.timedetector.TimeDetectorService;
+import com.android.server.timezonedetector.TimeZoneDetectorService;
+import com.android.server.timezonedetector.location.LocationTimeZoneManagerService;
import com.android.server.tracing.TracingServiceProxy;
+import com.android.server.translation.TranslationManagerService;
import com.android.server.trust.TrustManagerService;
import com.android.server.tv.TvInputManagerService;
import com.android.server.tv.TvRemoteService;
@@ -227,10 +270,15 @@ import com.android.server.tv.interactive.TvInteractiveAppManagerService;
import com.android.server.tv.tunerresourcemanager.TunerResourceManagerService;
import com.android.server.twilight.TwilightService;
import com.android.server.uri.UriGrantsManagerService;
+import com.android.server.usage.StorageStatsService;
import com.android.server.usage.UsageStatsService;
+import com.android.server.usb.UsbService;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.vibrator.VibratorManagerService;
+import com.android.server.voiceinteraction.VoiceInteractionManagerService;
import com.android.server.vr.VrManagerService;
+import com.android.server.wallpaper.WallpaperManagerService;
+import com.android.server.wallpapereffectsgeneration.WallpaperEffectsGenerationManagerService;
import com.android.server.wearable.WearableSensingManagerService;
import com.android.server.webkit.WebViewUpdateService;
import com.android.server.wm.ActivityTaskManagerService;
@@ -268,44 +316,20 @@ public final class SystemServer implements Dumpable {
* Implementation class names. TODO: Move them to a codegen class or load
* them from the build system somehow.
*/
- private static final String BACKUP_MANAGER_SERVICE_CLASS =
- "com.android.server.backup.BackupManagerService$Lifecycle";
- private static final String APPWIDGET_SERVICE_CLASS =
- "com.android.server.appwidget.AppWidgetService";
private static final String ARC_NETWORK_SERVICE_CLASS =
"com.android.server.arc.net.ArcNetworkService";
private static final String ARC_PERSISTENT_DATA_BLOCK_SERVICE_CLASS =
"com.android.server.arc.persistent_data_block.ArcPersistentDataBlockService";
private static final String ARC_SYSTEM_HEALTH_SERVICE =
"com.android.server.arc.health.ArcSystemHealthService";
- private static final String VOICE_RECOGNITION_MANAGER_SERVICE_CLASS =
- "com.android.server.voiceinteraction.VoiceInteractionManagerService";
- private static final String APP_HIBERNATION_SERVICE_CLASS =
- "com.android.server.apphibernation.AppHibernationService";
- private static final String PRINT_MANAGER_SERVICE_CLASS =
- "com.android.server.print.PrintManagerService";
- private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
- "com.android.server.companion.CompanionDeviceManagerService";
- private static final String VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS =
- "com.android.server.companion.virtual.VirtualDeviceManagerService";
private static final String STATS_COMPANION_APEX_PATH =
"/apex/com.android.os.statsd/javalib/service-statsd.jar";
+ private static final String STATS_COMPANION_LIFECYCLE_CLASS =
+ "com.android.server.stats.StatsCompanion$Lifecycle";
private static final String SCHEDULING_APEX_PATH =
"/apex/com.android.scheduling/javalib/service-scheduling.jar";
private static final String REBOOT_READINESS_LIFECYCLE_CLASS =
"com.android.server.scheduling.RebootReadinessManagerService$Lifecycle";
- private static final String CONNECTIVITY_SERVICE_APEX_PATH =
- "/apex/com.android.tethering/javalib/service-connectivity.jar";
- private static final String STATS_COMPANION_LIFECYCLE_CLASS =
- "com.android.server.stats.StatsCompanion$Lifecycle";
- private static final String STATS_PULL_ATOM_SERVICE_CLASS =
- "com.android.server.stats.pull.StatsPullAtomService";
- private static final String STATS_BOOTSTRAP_ATOM_SERVICE_LIFECYCLE_CLASS =
- "com.android.server.stats.bootstrap.StatsBootstrapAtomService$Lifecycle";
- private static final String USB_SERVICE_CLASS =
- "com.android.server.usb.UsbService$Lifecycle";
- private static final String MIDI_SERVICE_CLASS =
- "com.android.server.midi.MidiService$Lifecycle";
private static final String WIFI_APEX_SERVICE_JAR_PATH =
"/apex/com.android.wifi/javalib/service-wifi.jar";
private static final String WIFI_SERVICE_CLASS =
@@ -320,16 +344,6 @@ public final class SystemServer implements Dumpable {
"com.android.server.wifi.p2p.WifiP2pService";
private static final String LOWPAN_SERVICE_CLASS =
"com.android.server.lowpan.LowpanService";
- private static final String JOB_SCHEDULER_SERVICE_CLASS =
- "com.android.server.job.JobSchedulerService";
- private static final String LOCK_SETTINGS_SERVICE_CLASS =
- "com.android.server.locksettings.LockSettingsService$Lifecycle";
- private static final String STORAGE_MANAGER_SERVICE_CLASS =
- "com.android.server.StorageManagerService$Lifecycle";
- private static final String STORAGE_STATS_SERVICE_CLASS =
- "com.android.server.usage.StorageStatsService$Lifecycle";
- private static final String SEARCH_MANAGER_SERVICE_CLASS =
- "com.android.server.search.SearchManagerService$Lifecycle";
private static final String THERMAL_OBSERVER_CLASS =
"com.android.clockwork.ThermalObserver";
private static final String WEAR_CONNECTIVITY_SERVICE_CLASS =
@@ -354,91 +368,26 @@ public final class SystemServer implements Dumpable {
"com.android.clockwork.settings.WearSettingsService";
private static final String WRIST_ORIENTATION_SERVICE_CLASS =
"com.android.clockwork.wristorientation.WristOrientationService";
- private static final String ACCOUNT_SERVICE_CLASS =
- "com.android.server.accounts.AccountManagerService$Lifecycle";
- private static final String CONTENT_SERVICE_CLASS =
- "com.android.server.content.ContentService$Lifecycle";
- private static final String WALLPAPER_SERVICE_CLASS =
- "com.android.server.wallpaper.WallpaperManagerService$Lifecycle";
- private static final String AUTO_FILL_MANAGER_SERVICE_CLASS =
- "com.android.server.autofill.AutofillManagerService";
- private static final String CREDENTIAL_MANAGER_SERVICE_CLASS =
- "com.android.server.credentials.CredentialManagerService";
- private static final String CONTENT_CAPTURE_MANAGER_SERVICE_CLASS =
- "com.android.server.contentcapture.ContentCaptureManagerService";
- private static final String TRANSLATION_MANAGER_SERVICE_CLASS =
- "com.android.server.translation.TranslationManagerService";
- private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS =
- "com.android.server.musicrecognition.MusicRecognitionManagerService";
- private static final String AMBIENT_CONTEXT_MANAGER_SERVICE_CLASS =
- "com.android.server.ambientcontext.AmbientContextManagerService";
- private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
- "com.android.server.systemcaptions.SystemCaptionsManagerService";
- private static final String TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS =
- "com.android.server.texttospeech.TextToSpeechManagerService";
+
private static final String IOT_SERVICE_CLASS =
"com.android.things.server.IoTSystemService";
- private static final String SLICE_MANAGER_SERVICE_CLASS =
- "com.android.server.slice.SliceManagerService$Lifecycle";
private static final String CAR_SERVICE_HELPER_SERVICE_CLASS =
"com.android.internal.car.CarServiceHelperService";
- private static final String TIME_DETECTOR_SERVICE_CLASS =
- "com.android.server.timedetector.TimeDetectorService$Lifecycle";
- private static final String TIME_ZONE_DETECTOR_SERVICE_CLASS =
- "com.android.server.timezonedetector.TimeZoneDetectorService$Lifecycle";
- private static final String LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS =
- "com.android.server.timezonedetector.location.LocationTimeZoneManagerService$Lifecycle";
- private static final String GNSS_TIME_UPDATE_SERVICE_CLASS =
- "com.android.server.timedetector.GnssTimeUpdateService$Lifecycle";
- private static final String ACCESSIBILITY_MANAGER_SERVICE_CLASS =
- "com.android.server.accessibility.AccessibilityManagerService$Lifecycle";
- private static final String ADB_SERVICE_CLASS =
- "com.android.server.adb.AdbService$Lifecycle";
- private static final String SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS =
- "com.android.server.speech.SpeechRecognitionManagerService";
- private static final String WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS =
- "com.android.server.wallpapereffectsgeneration.WallpaperEffectsGenerationManagerService";
- private static final String APP_PREDICTION_MANAGER_SERVICE_CLASS =
- "com.android.server.appprediction.AppPredictionManagerService";
- private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
- "com.android.server.contentsuggestions.ContentSuggestionsManagerService";
- private static final String SEARCH_UI_MANAGER_SERVICE_CLASS =
- "com.android.server.searchui.SearchUiManagerService";
- private static final String SMARTSPACE_MANAGER_SERVICE_CLASS =
- "com.android.server.smartspace.SmartspaceManagerService";
- private static final String CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS =
- "com.android.server.contextualsearch.ContextualSearchManagerService";
- private static final String DEVICE_IDLE_CONTROLLER_CLASS =
- "com.android.server.DeviceIdleController";
- private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
- "com.android.server.blob.BlobStoreManagerService";
private static final String APPSEARCH_MODULE_LIFECYCLE_CLASS =
"com.android.server.appsearch.AppSearchModule$Lifecycle";
private static final String ISOLATED_COMPILATION_SERVICE_CLASS =
"com.android.server.compos.IsolatedCompilationService";
- private static final String ROLLBACK_MANAGER_SERVICE_CLASS =
- "com.android.server.rollback.RollbackManagerService";
- private static final String ALARM_MANAGER_SERVICE_CLASS =
- "com.android.server.alarm.AlarmManagerService";
- private static final String MEDIA_SESSION_SERVICE_CLASS =
- "com.android.server.media.MediaSessionService";
- private static final String MEDIA_RESOURCE_MONITOR_SERVICE_CLASS =
- "com.android.server.media.MediaResourceMonitorService";
+ private static final String CONNECTIVITY_SERVICE_APEX_PATH =
+ "/apex/com.android.tethering/javalib/service-connectivity.jar";
private static final String CONNECTIVITY_SERVICE_INITIALIZER_CLASS =
"com.android.server.ConnectivityServiceInitializer";
private static final String NETWORK_STATS_SERVICE_INITIALIZER_CLASS =
"com.android.server.NetworkStatsServiceInitializer";
- private static final String IP_CONNECTIVITY_METRICS_CLASS =
- "com.android.server.connectivity.IpConnectivityMetrics";
private static final String MEDIA_COMMUNICATION_SERVICE_CLASS =
"com.android.server.media.MediaCommunicationService";
- private static final String APP_COMPAT_OVERRIDES_SERVICE_CLASS =
- "com.android.server.compat.overrides.AppCompatOverridesService$Lifecycle";
private static final String HEALTHCONNECT_MANAGER_SERVICE_CLASS =
"com.android.server.healthconnect.HealthConnectManagerService";
private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
- private static final String GAME_MANAGER_SERVICE_CLASS =
- "com.android.server.app.GameManagerService$Lifecycle";
private static final String ENHANCED_CONFIRMATION_SERVICE_CLASS =
"com.android.ecm.EnhancedConfirmationService";
@@ -461,6 +410,7 @@ public final class SystemServer implements Dumpable {
+ "OnDevicePersonalizationSystemService$Lifecycle";
private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS =
"com.android.server.deviceconfig.DeviceConfigInit$Lifecycle";
+
private static final String DEVICE_LOCK_SERVICE_CLASS =
"com.android.server.devicelock.DeviceLockService";
private static final String DEVICE_LOCK_APEX_PATH =
@@ -1435,7 +1385,7 @@ public final class SystemServer implements Dumpable {
// Manages apk rollbacks.
t.traceBegin("StartRollbackManagerService");
- mSystemServiceManager.startService(ROLLBACK_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(RollbackManagerService.class);
t.traceEnd();
// Tracks native tombstones.
@@ -1580,11 +1530,11 @@ public final class SystemServer implements Dumpable {
// The AccountManager must come before the ContentService
t.traceBegin("StartAccountManagerService");
- mSystemServiceManager.startService(ACCOUNT_SERVICE_CLASS);
+ mSystemServiceManager.startService(AccountManagerService.Lifecycle.class);
t.traceEnd();
t.traceBegin("StartContentService");
- mSystemServiceManager.startService(CONTENT_SERVICE_CLASS);
+ mSystemServiceManager.startService(ContentService.Lifecycle.class);
t.traceEnd();
t.traceBegin("InstallSystemProviders");
@@ -1639,7 +1589,7 @@ public final class SystemServer implements Dumpable {
// TODO(aml-jobscheduler): Think about how to do it properly.
t.traceBegin("StartAlarmManagerService");
- mSystemServiceManager.startService(ALARM_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AlarmManagerService.class);
t.traceEnd();
t.traceBegin("StartInputManagerService");
@@ -1721,7 +1671,7 @@ public final class SystemServer implements Dumpable {
}
t.traceBegin("IpConnectivityMetrics");
- mSystemServiceManager.startService(IP_CONNECTIVITY_METRICS_CLASS);
+ mSystemServiceManager.startService(IpConnectivityMetrics.class);
t.traceEnd();
t.traceBegin("NetworkWatchlistService");
@@ -1796,7 +1746,7 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartAccessibilityManagerService");
try {
- mSystemServiceManager.startService(ACCESSIBILITY_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AccessibilityManagerService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting Accessibility Manager", e);
}
@@ -1819,7 +1769,7 @@ public final class SystemServer implements Dumpable {
* NotificationManagerService is dependant on StorageManagerService,
* (for media / usb notifications) so we must start StorageManagerService first.
*/
- mSystemServiceManager.startService(STORAGE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(StorageManagerService.Lifecycle.class);
storageManager = IStorageManager.Stub.asInterface(
ServiceManager.getService("mount"));
} catch (Throwable e) {
@@ -1829,7 +1779,7 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartStorageStatsService");
try {
- mSystemServiceManager.startService(STORAGE_STATS_SERVICE_CLASS);
+ mSystemServiceManager.startService(StorageStatsService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting StorageStatsService", e);
}
@@ -1860,7 +1810,7 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
t.traceBegin("StartAppHibernationService");
- mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS);
+ mSystemServiceManager.startService(AppHibernationService.class);
t.traceEnd();
t.traceBegin("ArtManagerLocal");
@@ -1892,7 +1842,7 @@ public final class SystemServer implements Dumpable {
} else {
t.traceBegin("StartLockSettingsService");
try {
- mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
+ mSystemServiceManager.startService(LockSettingsService.Lifecycle.class);
lockSettings = ILockSettings.Stub.asInterface(
ServiceManager.getService("lock_settings"));
} catch (Throwable e) {
@@ -1925,7 +1875,7 @@ public final class SystemServer implements Dumpable {
}
t.traceBegin("StartDeviceIdleController");
- mSystemServiceManager.startService(DEVICE_IDLE_CONTROLLER_CLASS);
+ mSystemServiceManager.startService(DeviceIdleController.class);
t.traceEnd();
// Always start the Device Policy Manager, so that the API is compatible with
@@ -1948,7 +1898,7 @@ public final class SystemServer implements Dumpable {
if (deviceHasConfigString(context,
R.string.config_defaultMusicRecognitionService)) {
t.traceBegin("StartMusicRecognitionManagerService");
- mSystemServiceManager.startService(MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(MusicRecognitionManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG,
@@ -1966,7 +1916,7 @@ public final class SystemServer implements Dumpable {
if (deviceHasConfigString(
context, R.string.config_defaultAmbientContextDetectionService)) {
t.traceBegin("StartAmbientContextService");
- mSystemServiceManager.startService(AMBIENT_CONTEXT_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AmbientContextManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "AmbientContextManagerService not defined by OEM or disabled by flag");
@@ -1974,13 +1924,13 @@ public final class SystemServer implements Dumpable {
// System Speech Recognition Service
t.traceBegin("StartSpeechRecognitionManagerService");
- mSystemServiceManager.startService(SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SpeechRecognitionManagerService.class);
t.traceEnd();
// App prediction manager service
if (deviceHasConfigString(context, R.string.config_defaultAppPredictionService)) {
t.traceBegin("StartAppPredictionService");
- mSystemServiceManager.startService(APP_PREDICTION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AppPredictionManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "AppPredictionService not defined by OEM");
@@ -1989,7 +1939,7 @@ public final class SystemServer implements Dumpable {
// Content suggestions manager service
if (deviceHasConfigString(context, R.string.config_defaultContentSuggestionsService)) {
t.traceBegin("StartContentSuggestionsService");
- mSystemServiceManager.startService(CONTENT_SUGGESTIONS_SERVICE_CLASS);
+ mSystemServiceManager.startService(ContentSuggestionsManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "ContentSuggestionsService not defined by OEM");
@@ -1998,14 +1948,14 @@ public final class SystemServer implements Dumpable {
// Search UI manager service
if (deviceHasConfigString(context, R.string.config_defaultSearchUiService)) {
t.traceBegin("StartSearchUiService");
- mSystemServiceManager.startService(SEARCH_UI_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SearchUiManagerService.class);
t.traceEnd();
}
// Smartspace manager service
if (deviceHasConfigString(context, R.string.config_defaultSmartspaceService)) {
t.traceBegin("StartSmartspaceService");
- mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SmartspaceManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "SmartspaceManagerService not defined by OEM or disabled by flag");
@@ -2015,7 +1965,7 @@ public final class SystemServer implements Dumpable {
if (deviceHasConfigString(context,
R.string.config_defaultContextualSearchPackageName)) {
t.traceBegin("StartContextualSearchService");
- mSystemServiceManager.startService(CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(ContextualSearchManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "ContextualSearchManagerService not defined or disabled by flag");
@@ -2214,7 +2164,7 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartTimeDetectorService");
try {
- mSystemServiceManager.startService(TIME_DETECTOR_SERVICE_CLASS);
+ mSystemServiceManager.startService(TimeDetectorService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting TimeDetectorService service", e);
}
@@ -2235,7 +2185,7 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartTimeZoneDetectorService");
try {
- mSystemServiceManager.startService(TIME_ZONE_DETECTOR_SERVICE_CLASS);
+ mSystemServiceManager.startService(TimeZoneDetectorService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting TimeZoneDetectorService service", e);
}
@@ -2251,7 +2201,7 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartLocationTimeZoneManagerService");
try {
- mSystemServiceManager.startService(LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(LocationTimeZoneManagerService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting LocationTimeZoneManagerService service", e);
}
@@ -2260,7 +2210,7 @@ public final class SystemServer implements Dumpable {
if (context.getResources().getBoolean(R.bool.config_enableGnssTimeUpdateService)) {
t.traceBegin("StartGnssTimeUpdateService");
try {
- mSystemServiceManager.startService(GNSS_TIME_UPDATE_SERVICE_CLASS);
+ mSystemServiceManager.startService(GnssTimeUpdateService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting GnssTimeUpdateService service", e);
}
@@ -2270,7 +2220,7 @@ public final class SystemServer implements Dumpable {
if (!isWatch) {
t.traceBegin("StartSearchManagerService");
try {
- mSystemServiceManager.startService(SEARCH_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SearchManagerService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting Search Service", e);
}
@@ -2279,7 +2229,7 @@ public final class SystemServer implements Dumpable {
if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) {
t.traceBegin("StartWallpaperManagerService");
- mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS);
+ mSystemServiceManager.startService(WallpaperManagerService.Lifecycle.class);
t.traceEnd();
} else {
Slog.i(TAG, "Wallpaper service disabled by config");
@@ -2289,8 +2239,7 @@ public final class SystemServer implements Dumpable {
if (deviceHasConfigString(context,
R.string.config_defaultWallpaperEffectsGenerationService)) {
t.traceBegin("StartWallpaperEffectsGenerationService");
- mSystemServiceManager.startService(
- WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(WallpaperEffectsGenerationManagerService.class);
t.traceEnd();
}
@@ -2345,14 +2294,14 @@ public final class SystemServer implements Dumpable {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
// Start MIDI Manager service
t.traceBegin("StartMidiManager");
- mSystemServiceManager.startService(MIDI_SERVICE_CLASS);
+ mSystemServiceManager.startService(MidiService.Lifecycle.class);
t.traceEnd();
}
// Start ADB Debugging Service
t.traceBegin("StartAdbService");
try {
- mSystemServiceManager.startService(ADB_SERVICE_CLASS);
+ mSystemServiceManager.startService(AdbService.Lifecycle.class);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting AdbService");
}
@@ -2364,7 +2313,7 @@ public final class SystemServer implements Dumpable {
|| Build.IS_EMULATOR) {
// Manage USB host and device support
t.traceBegin("StartUsbService");
- mSystemServiceManager.startService(USB_SERVICE_CLASS);
+ mSystemServiceManager.startService(UsbService.Lifecycle.class);
t.traceEnd();
}
@@ -2396,7 +2345,7 @@ public final class SystemServer implements Dumpable {
// TODO(aml-jobscheduler): Think about how to do it properly.
t.traceBegin("StartJobScheduler");
- mSystemServiceManager.startService(JOB_SCHEDULER_SERVICE_CLASS);
+ mSystemServiceManager.startService(JobSchedulerService.class);
t.traceEnd();
t.traceBegin("StartSoundTrigger");
@@ -2409,14 +2358,14 @@ public final class SystemServer implements Dumpable {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
t.traceBegin("StartBackupManager");
- mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(BackupManagerService.Lifecycle.class);
t.traceEnd();
}
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
|| context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
t.traceBegin("StartAppWidgetService");
- mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
+ mSystemServiceManager.startService(AppWidgetService.class);
t.traceEnd();
}
@@ -2425,7 +2374,7 @@ public final class SystemServer implements Dumpable {
// of initializing various settings. It will internally modify its behavior
// based on that feature.
t.traceBegin("StartVoiceRecognitionManager");
- mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(VoiceInteractionManagerService.class);
t.traceEnd();
if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
@@ -2486,7 +2435,7 @@ public final class SystemServer implements Dumpable {
}
t.traceBegin(START_BLOB_STORE_SERVICE);
- mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(BlobStoreManagerService.class);
t.traceEnd();
// Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
@@ -2507,7 +2456,7 @@ public final class SystemServer implements Dumpable {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PRINTING)) {
t.traceBegin("StartPrintManager");
- mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(PrintManagerService.class);
t.traceEnd();
}
@@ -2517,13 +2466,13 @@ public final class SystemServer implements Dumpable {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)) {
t.traceBegin("StartCompanionDeviceManager");
- mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(CompanionDeviceManagerService.class);
t.traceEnd();
}
if (context.getResources().getBoolean(R.bool.config_enableVirtualDeviceManager)) {
t.traceBegin("StartVirtualDeviceManager");
- mSystemServiceManager.startService(VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(VirtualDeviceManagerService.class);
t.traceEnd();
}
@@ -2532,7 +2481,7 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
t.traceBegin("StartMediaSessionService");
- mSystemServiceManager.startService(MEDIA_SESSION_SERVICE_CLASS);
+ mSystemServiceManager.startService(MediaSessionService.class);
t.traceEnd();
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
@@ -2563,7 +2512,7 @@ public final class SystemServer implements Dumpable {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
t.traceBegin("StartMediaResourceMonitor");
- mSystemServiceManager.startService(MEDIA_RESOURCE_MONITOR_SERVICE_CLASS);
+ mSystemServiceManager.startService(MediaResourceMonitorService.class);
t.traceEnd();
}
@@ -2735,7 +2684,7 @@ public final class SystemServer implements Dumpable {
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED)) {
t.traceBegin("StartSliceManagerService");
- mSystemServiceManager.startService(SLICE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SliceManagerService.Lifecycle.class);
t.traceEnd();
}
@@ -2759,12 +2708,12 @@ public final class SystemServer implements Dumpable {
// Statsd pulled atoms
t.traceBegin("StartStatsPullAtomService");
- mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS);
+ mSystemServiceManager.startService(StatsPullAtomService.class);
t.traceEnd();
// Log atoms to statsd from bootstrap processes.
t.traceBegin("StatsBootstrapAtomService");
- mSystemServiceManager.startService(STATS_BOOTSTRAP_ATOM_SERVICE_LIFECYCLE_CLASS);
+ mSystemServiceManager.startService(StatsBootstrapAtomService.Lifecycle.class);
t.traceEnd();
// Incidentd and dumpstated helper
@@ -2811,7 +2760,7 @@ public final class SystemServer implements Dumpable {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOFILL)) {
t.traceBegin("StartAutoFillService");
- mSystemServiceManager.startService(AUTO_FILL_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AutofillManagerService.class);
t.traceEnd();
}
@@ -2820,13 +2769,12 @@ public final class SystemServer implements Dumpable {
DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CREDENTIAL,
CredentialManager.DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true);
if (credentialManagerEnabled) {
- if(isWatch &&
- !android.credentials.flags.Flags.wearCredentialManagerEnabled()) {
- Slog.d(TAG, "CredentialManager disabled on wear.");
+ if (isWatch && !android.credentials.flags.Flags.wearCredentialManagerEnabled()) {
+ Slog.d(TAG, "CredentialManager disabled on wear.");
} else {
- t.traceBegin("StartCredentialManagerService");
- mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
- t.traceEnd();
+ t.traceBegin("StartCredentialManagerService");
+ mSystemServiceManager.startService(CredentialManagerService.class);
+ t.traceEnd();
}
} else {
Slog.d(TAG, "CredentialManager disabled.");
@@ -2836,7 +2784,7 @@ public final class SystemServer implements Dumpable {
// Translation manager service
if (deviceHasConfigString(context, R.string.config_defaultTranslationService)) {
t.traceBegin("StartTranslationManagerService");
- mSystemServiceManager.startService(TRANSLATION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(TranslationManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "TranslationService not defined by OEM");
@@ -2980,7 +2928,7 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
t.traceBegin("GameManagerService");
- mSystemServiceManager.startService(GAME_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(GameManagerService.Lifecycle.class);
t.traceEnd();
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)) {
@@ -3012,7 +2960,7 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
t.traceBegin("AppCompatOverridesService");
- mSystemServiceManager.startService(APP_COMPAT_OVERRIDES_SERVICE_CLASS);
+ mSystemServiceManager.startService(AppCompatOverridesService.Lifecycle.class);
t.traceEnd();
t.traceBegin("HealthConnectManagerService");
@@ -3397,14 +3345,14 @@ public final class SystemServer implements Dumpable {
}
t.traceBegin("StartSystemCaptionsManagerService");
- mSystemServiceManager.startService(SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SystemCaptionsManagerService.class);
t.traceEnd();
}
private void startTextToSpeechManagerService(@NonNull Context context,
@NonNull TimingsTraceAndSlog t) {
t.traceBegin("StartTextToSpeechManagerService");
- mSystemServiceManager.startService(TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(TextToSpeechManagerService.class);
t.traceEnd();
}
@@ -3439,7 +3387,7 @@ public final class SystemServer implements Dumpable {
}
t.traceBegin("StartContentCaptureService");
- mSystemServiceManager.startService(CONTENT_CAPTURE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(ContentCaptureManagerService.class);
ContentCaptureManagerInternal ccmi =
LocalServices.getService(ContentCaptureManagerInternal.class);
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
index 854bc0f86bd4..4b578afddad2 100644
--- a/services/java/com/android/server/flags.aconfig
+++ b/services/java/com/android/server/flags.aconfig
@@ -1,5 +1,4 @@
package: "android.server"
-container: "system"
flag {
namespace: "system_performance"
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 36758341d4d7..69a88e982f96 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -16,7 +16,6 @@
package com.android.server.permission.access
-import android.permission.flags.Flags
import android.util.Slog
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
@@ -79,7 +78,7 @@ private constructor(
setPackageStates(packageStates)
setDisabledSystemPackageStates(disabledSystemPackageStates)
packageStates.forEach { (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
mutateAppIdPackageNames()
@@ -107,7 +106,7 @@ private constructor(
newState.mutateUserStatesNoWrite()[userId] = MutableUserState()
forEachSchemePolicy { with(it) { onUserAdded(userId) } }
newState.externalState.packageStates.forEach { (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
upgradePackageVersion(packageState, userId)
@@ -133,7 +132,7 @@ private constructor(
setPackageStates(packageStates)
setDisabledSystemPackageStates(disabledSystemPackageStates)
packageStates.forEach { (packageName, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
if (packageState.volumeUuid == volumeUuid) {
@@ -161,7 +160,7 @@ private constructor(
with(it) { onStorageVolumeMounted(volumeUuid, packageNames, isSystemUpdated) }
}
packageStates.forEach { (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
if (packageState.volumeUuid == volumeUuid) {
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 63fb468c8f54..87af841b901c 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -81,7 +81,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
override fun MutateStateScope.onUserAdded(userId: Int) {
newState.externalState.packageStates.forEach { (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
evaluateAllPermissionStatesForPackageAndUser(packageState, userId, null)
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 11893e7d7577..65feeb027b3e 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -1445,7 +1445,7 @@ class PermissionService(private val service: AccessCheckingService) :
val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates }
service.mutateState {
packageStates.forEach { (packageName, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
val androidPackage = packageState.androidPackage ?: return@forEach
@@ -1601,7 +1601,7 @@ class PermissionService(private val service: AccessCheckingService) :
) {
with(policy) { getPermissionFlags(appId, userId, permissionName) }
} else {
- if (permissionName !in DEVICE_AWARE_PERMISSIONS) {
+ if (permissionName !in PermissionManager.DEVICE_AWARE_PERMISSIONS) {
Slog.i(
LOG_TAG,
"$permissionName is not device aware permission, " +
@@ -1626,7 +1626,7 @@ class PermissionService(private val service: AccessCheckingService) :
) {
with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
} else {
- if (permissionName !in DEVICE_AWARE_PERMISSIONS) {
+ if (permissionName !in PermissionManager.DEVICE_AWARE_PERMISSIONS) {
Slog.i(
LOG_TAG,
"$permissionName is not device aware permission, " +
@@ -1880,7 +1880,7 @@ class PermissionService(private val service: AccessCheckingService) :
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
service.mutateState {
snapshot.packageStates.forEach { (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
with(policy) { resetRuntimePermissions(packageState.packageName, userId) }
@@ -1925,7 +1925,7 @@ class PermissionService(private val service: AccessCheckingService) :
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
snapshot.packageStates.forEach { (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
val androidPackage = packageState.androidPackage ?: return@forEach
@@ -1943,7 +1943,7 @@ class PermissionService(private val service: AccessCheckingService) :
val permissions = service.getState { with(policy) { getPermissions() } }
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
snapshot.packageStates.forEach packageStates@{ (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@packageStates
}
val androidPackage = packageState.androidPackage ?: return@packageStates
@@ -2072,7 +2072,7 @@ class PermissionService(private val service: AccessCheckingService) :
val appIdPackageNames = MutableIndexedMap<Int, MutableIndexedSet<String>>()
packageStates.forEach { (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
appIdPackageNames
@@ -2328,7 +2328,7 @@ class PermissionService(private val service: AccessCheckingService) :
isInstantApp: Boolean,
oldPackage: AndroidPackage?
) {
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return
}
@@ -2358,7 +2358,7 @@ class PermissionService(private val service: AccessCheckingService) :
params: PermissionManagerServiceInternal.PackageInstalledParams,
userId: Int
) {
- if (Flags.ignoreApexPermissions() && androidPackage.isApex) {
+ if (androidPackage.isApex) {
return
}
@@ -2414,7 +2414,7 @@ class PermissionService(private val service: AccessCheckingService) :
sharedUserPkgs: List<AndroidPackage>,
userId: Int
) {
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return
}
@@ -2847,15 +2847,6 @@ class PermissionService(private val service: AccessCheckingService) :
PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or
PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
- /** These permissions are supported for virtual devices. */
- // TODO: b/298661870 - Use new API to get the list of device aware permissions.
- val DEVICE_AWARE_PERMISSIONS =
- if (Flags.deviceAwarePermissionsEnabled()) {
- setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
- } else {
- emptySet<String>()
- }
-
fun getFullerPermission(permissionName: String): String? =
FULLER_PERMISSIONS[permissionName]
}
diff --git a/services/proguard.flags b/services/proguard.flags
index a01e7dc16147..bf30781b434e 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -29,17 +29,6 @@
public protected *;
}
-# Derivatives of SystemService and other services created via reflection
--keep,allowoptimization,allowaccessmodification class * extends com.android.server.SystemService {
- public <methods>;
-}
--keep,allowoptimization,allowaccessmodification class * extends com.android.server.devicepolicy.BaseIDevicePolicyManager {
- public <init>(...);
-}
--keep,allowoptimization,allowaccessmodification class com.android.server.wallpaper.WallpaperManagerService {
- public <init>(...);
-}
-
# Accessed from com.android.compos APEX
-keep,allowoptimization,allowaccessmodification class com.android.internal.art.ArtStatsLog {
public static void write(...);
@@ -68,13 +57,6 @@
-keep public class android.hidl.manager.** { *; }
-keep public class com.android.server.wm.WindowManagerInternal { *; }
-# Notification extractors
-# TODO(b/210510433): Revisit and consider generating from frameworks/base/core/res/res/values/config.xml.
--keep,allowoptimization,allowaccessmodification public class com.android.server.notification.** implements com.android.server.notification.NotificationSignalExtractor
-
-# OEM provided DisplayAreaPolicy.Provider defined in frameworks/base/core/res/res/values/config.xml.
--keep,allowoptimization,allowaccessmodification class com.android.server.wm.** implements com.android.server.wm.DisplayAreaPolicy$Provider
-
# JNI keep rules
# The global keep rule for native methods allows stripping of such methods if they're unreferenced
# in Java. However, because system_server explicitly registers these methods from native code,
@@ -118,9 +100,6 @@
# Miscellaneous reflection keep rules
# TODO(b/210510433): Revisit and fix with @Keep.
--keep,allowoptimization,allowaccessmodification class com.android.server.usage.AppStandbyController {
- public <init>(...);
-}
-keep,allowoptimization,allowaccessmodification class android.hardware.usb.gadget.** { *; }
# Needed when optimizations enabled
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index 94ee0a871448..a547d0f94ea3 100644
--- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -17,9 +17,7 @@
package com.android.server.backup;
import static android.Manifest.permission.BACKUP;
-import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.PACKAGE_USAGE_STATS;
import static com.android.server.backup.testing.TransportData.backupTransport;
@@ -73,10 +71,7 @@ import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowContextWrapper;
import java.io.File;
-import java.io.FileDescriptor;
import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
/** Tests for {@link BackupManagerService}. */
@RunWith(RobolectricTestRunner.class)
@@ -1461,67 +1456,12 @@ public class BackupManagerServiceRoboTest {
// Service tests
// ---------------------------------------------
- /** Test that the backup service routes methods correctly to the user that requests it. */
- @Test
- public void testDump_onRegisteredUser_callsMethodForUser() throws Exception {
- grantDumpPermissions();
- BackupManagerService backupManagerService = createSystemRegisteredService();
- File testFile = createTestFile();
- FileDescriptor fileDescriptor = new FileDescriptor();
- PrintWriter printWriter = new PrintWriter(testFile);
- String[] args = {"1", "2"};
- ShadowBinder.setCallingUserHandle(UserHandle.of(UserHandle.USER_SYSTEM));
-
- backupManagerService.dump(fileDescriptor, printWriter, args);
-
- verify(mUserSystemService).dump(fileDescriptor, printWriter, args);
- }
-
- /** Test that the backup service does not route methods for non-registered users. */
- @Test
- public void testDump_onUnknownUser_doesNotPropagateCall() throws Exception {
- grantDumpPermissions();
- BackupManagerService backupManagerService = createService();
- File testFile = createTestFile();
- FileDescriptor fileDescriptor = new FileDescriptor();
- PrintWriter printWriter = new PrintWriter(testFile);
- String[] args = {"1", "2"};
-
- backupManagerService.dump(fileDescriptor, printWriter, args);
-
- verify(mUserOneService, never()).dump(fileDescriptor, printWriter, args);
- }
-
- /** Test that 'dumpsys backup users' dumps the list of users registered in backup service*/
- @Test
- public void testDump_users_dumpsListOfRegisteredUsers() {
- grantDumpPermissions();
- BackupManagerService backupManagerService = createSystemRegisteredService();
- registerUser(backupManagerService, mUserOneId, mUserOneService);
- StringWriter out = new StringWriter();
- PrintWriter writer = new PrintWriter(out);
- String[] args = {"users"};
-
- backupManagerService.dump(null, writer, args);
-
- writer.flush();
- assertEquals(
- String.format("%s %d %d\n", BackupManagerService.DUMP_RUNNING_USERS_MESSAGE,
- UserHandle.USER_SYSTEM, mUserOneId),
- out.toString());
- }
-
private File createTestFile() throws IOException {
File testFile = new File(mContext.getFilesDir(), "test");
testFile.createNewFile();
return testFile;
}
- private void grantDumpPermissions() {
- mShadowContext.grantPermissions(DUMP);
- mShadowContext.grantPermissions(PACKAGE_USAGE_STATS);
- }
-
/**
* Test that the backup services throws a {@link SecurityException} if the caller does not have
* INTERACT_ACROSS_USERS_FULL permission and passes a different user id.
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index ad4d91ff8ba0..6d89e80e5480 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -27,6 +27,8 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Objects;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DisplayBrightnessStateTest {
@@ -101,7 +103,9 @@ public class DisplayBrightnessStateTest {
.append("\n customAnimationRate:")
.append(displayBrightnessState.getCustomAnimationRate())
.append("\n shouldUpdateScreenBrightnessSetting:")
- .append(displayBrightnessState.shouldUpdateScreenBrightnessSetting());
+ .append(displayBrightnessState.shouldUpdateScreenBrightnessSetting())
+ .append("\n mBrightnessEvent:")
+ .append(Objects.toString(displayBrightnessState.getBrightnessEvent(), "null"));
return sb.toString();
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index b80d44fb8fd8..5897d76663c5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -924,7 +924,7 @@ public final class DisplayDeviceConfigTest {
getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ true));
assertTrue(mDisplayDeviceConfig.isEvenDimmerAvailable());
- assertEquals(0.0001f, mDisplayDeviceConfig.getBacklightFromBrightness(0.1f), ZERO_DELTA);
+ assertEquals(0.01f, mDisplayDeviceConfig.getBacklightFromBrightness(0.002f), ZERO_DELTA);
assertEquals(0.2f, mDisplayDeviceConfig.getNitsFromBacklight(0.0f), ZERO_DELTA);
}
@@ -1649,18 +1649,28 @@ public final class DisplayDeviceConfigTest {
private String evenDimmerConfig(boolean enabled) {
return (enabled ? "<evenDimmer enabled=\"true\">" : "<evenDimmer enabled=\"false\">")
+ " <transitionPoint>0.1</transitionPoint>\n"
- + " <nits>0.2</nits>\n"
- + " <nits>2.0</nits>\n"
- + " <nits>500.0</nits>\n"
- + " <nits>1000.0</nits>\n"
- + " <backlight>0</backlight>\n"
- + " <backlight>0.0001</backlight>\n"
- + " <backlight>0.5</backlight>\n"
- + " <backlight>1.0</backlight>\n"
- + " <brightness>0</brightness>\n"
- + " <brightness>0.1</brightness>\n"
- + " <brightness>0.5</brightness>\n"
- + " <brightness>1.0</brightness>\n"
+ + " <brightnessMapping>\n"
+ + " <brightnessPoint>\n"
+ + " <nits>0.2</nits>\n"
+ + " <backlight>0</backlight>\n"
+ + " <brightness>0</brightness>\n"
+ + " </brightnessPoint>\n"
+ + " <brightnessPoint>\n"
+ + " <nits>2.0</nits>\n"
+ + " <backlight>0.01</backlight>\n"
+ + " <brightness>0.002</brightness>\n"
+ + " </brightnessPoint>\n"
+ + " <brightnessPoint>\n"
+ + " <nits>500.0</nits>\n"
+ + " <backlight>0.5</backlight>\n"
+ + " <brightness>0.5</brightness>\n"
+ + " </brightnessPoint>\n"
+ + " <brightnessPoint>\n"
+ + " <nits>1000</nits>\n"
+ + " <backlight>1.0</backlight>\n"
+ + " <brightness>1.0</brightness>\n"
+ + " </brightnessPoint>\n"
+ + " </brightnessMapping>\n"
+ " <luxToMinimumNitsMap>\n"
+ " <point>\n"
+ " <value>10</value> <nits>0.3</nits>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index b7fa7ead4c81..b0eee0881f63 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -133,8 +133,8 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.internal.util.test.LocalServiceKeeperRule;
import com.android.modules.utils.testing.ExtendedMockitoRule;
-import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.display.DisplayManagerService.DeviceStateListener;
@@ -218,6 +218,9 @@ public class DisplayManagerServiceTest {
@Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+ @Rule(order = 2)
+ public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
+
private Context mContext;
private Resources mResources;
@@ -354,6 +357,7 @@ public class DisplayManagerServiceTest {
@Mock SensorManager mSensorManager;
@Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
@Mock PackageManagerInternal mMockPackageManagerInternal;
+ @Mock DisplayManagerInternal mMockDisplayManagerInternal;
@Mock DisplayAdapter mMockDisplayAdapter;
@Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
@@ -371,20 +375,20 @@ public class DisplayManagerServiceTest {
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(false);
mSetFlagsRule.disableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
- LocalServices.removeServiceForTest(InputManagerInternal.class);
- LocalServices.addService(InputManagerInternal.class, mMockInputManagerInternal);
- LocalServices.removeServiceForTest(WindowManagerInternal.class);
- LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerInternal);
- LocalServices.removeServiceForTest(LightsManager.class);
- LocalServices.addService(LightsManager.class, mMockLightsManager);
- LocalServices.removeServiceForTest(SensorManagerInternal.class);
- LocalServices.addService(SensorManagerInternal.class, mMockSensorManagerInternal);
- LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
- LocalServices.addService(
+ mLocalServiceKeeperRule.overrideLocalService(
+ InputManagerInternal.class, mMockInputManagerInternal);
+ mLocalServiceKeeperRule.overrideLocalService(
+ WindowManagerInternal.class, mMockWindowManagerInternal);
+ mLocalServiceKeeperRule.overrideLocalService(
+ LightsManager.class, mMockLightsManager);
+ mLocalServiceKeeperRule.overrideLocalService(
+ SensorManagerInternal.class, mMockSensorManagerInternal);
+ mLocalServiceKeeperRule.overrideLocalService(
VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
- LocalServices.removeServiceForTest(PackageManagerInternal.class);
- LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
- // TODO: b/287945043
+ mLocalServiceKeeperRule.overrideLocalService(
+ PackageManagerInternal.class, mMockPackageManagerInternal);
+ mLocalServiceKeeperRule.overrideLocalService(
+ DisplayManagerInternal.class, mMockDisplayManagerInternal);
Display display = mock(Display.class);
when(display.getDisplayAdjustments()).thenReturn(new DisplayAdjustments());
when(display.getBrightnessInfo()).thenReturn(mock(BrightnessInfo.class));
@@ -960,6 +964,9 @@ public class DisplayManagerServiceTest {
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
VIRTUAL_DISPLAY_NAME, width, height, dpi);
builder.setUniqueId(uniqueId);
+ builder.setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
+ when(mContext.checkCallingPermission(CAPTURE_VIDEO_OUTPUT))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
@@ -972,6 +979,7 @@ public class DisplayManagerServiceTest {
VIRTUAL_DISPLAY_NAME, width, height, dpi).setUniqueId(uniqueId2);
builder2.setUniqueId(uniqueId2);
builder2.setDisplayIdToMirror(firstDisplayId);
+ builder2.setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
mMockAppToken2 /* callback */, null /* projection */,
PACKAGE_NAME);
@@ -2448,8 +2456,9 @@ public class DisplayManagerServiceTest {
LogicalDisplay display =
logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
assertThat(display.isEnabledLocked()).isFalse();
+ // TODO(b/332711269) make sure only one DISPLAY_GROUP_EVENT_ADDED sent.
assertThat(callback.receivedEvents()).containsExactly(DISPLAY_GROUP_EVENT_ADDED,
- EVENT_DISPLAY_CONNECTED).inOrder();
+ DISPLAY_GROUP_EVENT_ADDED, EVENT_DISPLAY_CONNECTED).inOrder();
}
@Test
@@ -2624,11 +2633,19 @@ public class DisplayManagerServiceTest {
// Create default display device
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
callback.waitForExpectedEvent();
+
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
FakeDisplayDevice displayDevice =
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+ callback.waitForExpectedEvent();
+
+ callback.expectsEvent(EVENT_DISPLAY_REMOVED);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ callback.waitForExpectedEvent();
+
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
LogicalDisplay display =
logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
- callback.expectsEvent(EVENT_DISPLAY_ADDED);
logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ true);
logicalDisplayMapper.updateLogicalDisplays();
callback.waitForExpectedEvent();
@@ -2651,6 +2668,7 @@ public class DisplayManagerServiceTest {
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
callback.expectsEvent(EVENT_DISPLAY_ADDED);
// Create default display device
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
@@ -2664,7 +2682,6 @@ public class DisplayManagerServiceTest {
logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ true);
logicalDisplayMapper.updateLogicalDisplays();
callback.waitForExpectedEvent();
- callback.clear();
assertThrows(SecurityException.class, () -> bs.disableConnectedDisplay(displayId));
}
@@ -2835,6 +2852,7 @@ public class DisplayManagerServiceTest {
float brightness1 = 0.3f;
float brightness2 = 0.45f;
+ waitForIdleHandler(mPowerHandler);
int userId1 = 123;
int userId2 = 456;
@@ -2844,8 +2862,8 @@ public class DisplayManagerServiceTest {
userInfo2.id = userId2;
when(mUserManager.getUserSerialNumber(userId1)).thenReturn(12345);
when(mUserManager.getUserSerialNumber(userId2)).thenReturn(45678);
- final SystemService.TargetUser from = new SystemService.TargetUser(userInfo1);
- final SystemService.TargetUser to = new SystemService.TargetUser(userInfo2);
+ final SystemService.TargetUser user1 = new SystemService.TargetUser(userInfo1);
+ final SystemService.TargetUser user2 = new SystemService.TargetUser(userInfo2);
// The same brightness will be restored for a user only if auto-brightness is off,
// otherwise the current lux will be used to determine the brightness.
@@ -2853,20 +2871,20 @@ public class DisplayManagerServiceTest {
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
- displayManager.onUserSwitching(to, from);
+ displayManager.onUserSwitching(/* from= */ user2, /* to= */ user1);
waitForIdleHandler(mPowerHandler);
displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightness1);
- displayManager.onUserSwitching(from, to);
+ displayManager.onUserSwitching(/* from= */ user1, /* to= */ user2);
waitForIdleHandler(mPowerHandler);
displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightness2);
- displayManager.onUserSwitching(to, from);
+ displayManager.onUserSwitching(/* from= */ user2, /* to= */ user1);
waitForIdleHandler(mPowerHandler);
assertEquals(brightness1,
displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
FLOAT_TOLERANCE);
- displayManager.onUserSwitching(from, to);
+ displayManager.onUserSwitching(/* from= */ user1, /* to= */ user2);
waitForIdleHandler(mPowerHandler);
assertEquals(brightness2,
displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
@@ -3000,6 +3018,7 @@ public class DisplayManagerServiceTest {
new DisplayManagerService(mContext, mBasicInjector);
displayManager.systemReady(false /* safeMode */);
+ displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
ArgumentMatcher<IntentFilter> matchesFilter =
(filter) -> Intent.ACTION_SETTING_RESTORED.equals(filter.getAction(0));
verify(mContext, times(0)).registerReceiver(any(BroadcastReceiver.class),
@@ -3365,7 +3384,7 @@ public class DisplayManagerServiceTest {
void waitForExpectedEvent(Duration timeout) {
try {
- assertWithMessage("Event '" + mExpectedEvent + "' is received.")
+ assertWithMessage("Expected '" + mExpectedEvent + "'")
.that(mLatch.await(timeout.toMillis(), TimeUnit.MILLISECONDS)).isTrue();
} catch (InterruptedException ex) {
throw new AssertionError("Waiting for expected event interrupted", ex);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index 1a71e77a3b1b..ea08be4f1be4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -317,7 +317,8 @@ public class ExternalDisplayPolicyTest {
mDisplayEventCaptor.capture());
assertThat(mLogicalDisplayCaptor.getValue()).isEqualTo(mMockedLogicalDisplay);
assertThat(mDisplayEventCaptor.getValue()).isEqualTo(EVENT_DISPLAY_CONNECTED);
- verify(mMockedLogicalDisplay).setEnabledLocked(false);
+ verify(mMockedLogicalDisplayMapper).setDisplayEnabledLocked(eq(mMockedLogicalDisplay),
+ eq(false));
clearInvocations(mMockedLogicalDisplayMapper);
clearInvocations(mMockedLogicalDisplay);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 6ed82383ca32..1ae4099e41d4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -19,7 +19,7 @@ package com.android.server.display.brightness;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -111,8 +111,7 @@ public final class DisplayBrightnessControllerTest {
DisplayBrightnessStrategy displayBrightnessStrategy = mock(DisplayBrightnessStrategy.class);
int targetDisplayState = Display.STATE_DOZE;
when(mDisplayBrightnessStrategySelector.selectStrategy(
- eq(new StrategySelectionRequest(displayPowerRequest, targetDisplayState))))
- .thenReturn(displayBrightnessStrategy);
+ any(StrategySelectionRequest.class))).thenReturn(displayBrightnessStrategy);
mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState);
verify(displayBrightnessStrategy).updateBrightness(displayPowerRequest);
assertEquals(mDisplayBrightnessController.getCurrentDisplayBrightnessStrategy(),
@@ -164,6 +163,7 @@ public final class DisplayBrightnessControllerTest {
// No brightness is set if the pending brightness is invalid
mDisplayBrightnessController.setPendingScreenBrightness(Float.NaN);
assertFalse(mDisplayBrightnessController.updateUserSetScreenBrightness());
+ assertFalse(mDisplayBrightnessController.getIsUserSetScreenBrightnessUpdated());
// user set brightness is not set if the current and the pending brightness are same.
float currentBrightness = 0.4f;
@@ -175,6 +175,7 @@ public final class DisplayBrightnessControllerTest {
mDisplayBrightnessController.setPendingScreenBrightness(currentBrightness);
mDisplayBrightnessController.setTemporaryBrightness(currentBrightness);
assertFalse(mDisplayBrightnessController.updateUserSetScreenBrightness());
+ assertFalse(mDisplayBrightnessController.getIsUserSetScreenBrightnessUpdated());
verify(temporaryBrightnessStrategy).setTemporaryScreenBrightness(
PowerManager.BRIGHTNESS_INVALID_FLOAT);
assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
@@ -188,6 +189,7 @@ public final class DisplayBrightnessControllerTest {
mDisplayBrightnessController.setPendingScreenBrightness(pendingScreenBrightness);
mDisplayBrightnessController.setTemporaryBrightness(temporaryScreenBrightness);
assertTrue(mDisplayBrightnessController.updateUserSetScreenBrightness());
+ assertTrue(mDisplayBrightnessController.getIsUserSetScreenBrightnessUpdated());
assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
pendingScreenBrightness, /* delta= */ 0.0f);
assertEquals(mDisplayBrightnessController.getLastUserSetScreenBrightness(),
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index 4c9dd58a1250..b8858cc01b06 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.content.ContentResolver;
@@ -40,6 +41,7 @@ import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2;
import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
@@ -80,6 +82,8 @@ public final class DisplayBrightnessStrategySelectorTest {
@Mock
private AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
@Mock
+ private AutomaticBrightnessStrategy2 mAutomaticBrightnessStrategy2;
+ @Mock
private OffloadBrightnessStrategy mOffloadBrightnessStrategy;
@Mock
private Resources mResources;
@@ -126,12 +130,18 @@ public final class DisplayBrightnessStrategySelectorTest {
}
@Override
- AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context,
+ AutomaticBrightnessStrategy getAutomaticBrightnessStrategy1(Context context,
int displayId) {
return mAutomaticBrightnessStrategy;
}
@Override
+ AutomaticBrightnessStrategy2 getAutomaticBrightnessStrategy2(Context context,
+ int displayId) {
+ return mAutomaticBrightnessStrategy2;
+ }
+
+ @Override
OffloadBrightnessStrategy getOffloadBrightnessStrategy() {
return mOffloadBrightnessStrategy;
}
@@ -162,7 +172,8 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
- new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE)),
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE,
+ 0.1f, false)),
mDozeBrightnessModeStrategy);
}
@@ -175,7 +186,8 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy(
- new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE)),
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE,
+ 0.1f, false)),
mDozeBrightnessModeStrategy);
}
@@ -184,7 +196,8 @@ public final class DisplayBrightnessStrategySelectorTest {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
DisplayManagerInternal.DisplayPowerRequest.class);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
- new StrategySelectionRequest(displayPowerRequest, Display.STATE_OFF)),
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_OFF,
+ 0.1f, false)),
mScreenOffBrightnessModeStrategy);
}
@@ -195,7 +208,8 @@ public final class DisplayBrightnessStrategySelectorTest {
displayPowerRequest.screenBrightnessOverride = 0.4f;
when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
- new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON)),
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+ 0.1f, false)),
mOverrideBrightnessStrategy);
}
@@ -207,7 +221,8 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(0.3f);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
- new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON)),
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+ 0.1f, false)),
mTemporaryBrightnessStrategy);
}
@@ -220,7 +235,8 @@ public final class DisplayBrightnessStrategySelectorTest {
displayPowerRequest.screenBrightnessOverride = Float.NaN;
when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
- new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON)),
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+ 0.1f, false)),
mBoostBrightnessStrategy);
}
@@ -233,7 +249,8 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(Float.NaN);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
- new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON)),
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+ 0.1f, false)),
mInvalidBrightnessStrategy);
}
@@ -243,7 +260,8 @@ public final class DisplayBrightnessStrategySelectorTest {
DisplayManagerInternal.DisplayPowerRequest.class);
when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
- new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON)),
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+ 0.1f, false)),
mFollowerBrightnessStrategy);
}
@@ -257,14 +275,39 @@ public final class DisplayBrightnessStrategySelectorTest {
displayPowerRequest.screenBrightnessOverride = Float.NaN;
when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
- when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true);
+ when(mAutomaticBrightnessStrategy2.shouldUseAutoBrightness()).thenReturn(true);
when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
- new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON)),
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+ 0.1f, false)),
mOffloadBrightnessStrategy);
}
@Test
+ public void selectStrategy_selectsAutomaticStrategyWhenValid() {
+ when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
+ mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+ mInjector, DISPLAY_ID, mDisplayManagerFlags);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ displayPowerRequest.screenBrightnessOverride = Float.NaN;
+ when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+ when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+ when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true);
+ when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(true);
+ assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+ 0.1f, false)),
+ mAutomaticBrightnessStrategy);
+ verifyZeroInteractions(mOffloadBrightnessStrategy);
+ verify(mAutomaticBrightnessStrategy).setAutoBrightnessState(Display.STATE_ON,
+ false, BrightnessReason.REASON_UNKNOWN,
+ DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT, 0.1f, false);
+
+ }
+
+ @Test
public void selectStrategyDoesNotSelectOffloadStrategyWhenFeatureFlagDisabled() {
when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(false);
mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
@@ -277,7 +320,8 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
assertNotEquals(mOffloadBrightnessStrategy,
mDisplayBrightnessStrategySelector.selectStrategy(
- new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON)));
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+ 0.1f, false)));
}
@Test
@@ -290,10 +334,13 @@ public final class DisplayBrightnessStrategySelectorTest {
when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f);
mDisplayBrightnessStrategySelector.selectStrategy(
- new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON));
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+ 0.1f, false));
StrategySelectionNotifyRequest strategySelectionNotifyRequest =
- new StrategySelectionNotifyRequest(mFollowerBrightnessStrategy);
+ new StrategySelectionNotifyRequest(displayPowerRequest, Display.STATE_ON,
+ mFollowerBrightnessStrategy, 0.1f,
+ false, false);
verify(mInvalidBrightnessStrategy).strategySelectionPostProcessor(
eq(strategySelectionNotifyRequest));
verify(mScreenOffBrightnessModeStrategy).strategySelectionPostProcessor(
@@ -308,5 +355,22 @@ public final class DisplayBrightnessStrategySelectorTest {
eq(strategySelectionNotifyRequest));
verify(mTemporaryBrightnessStrategy).strategySelectionPostProcessor(
eq(strategySelectionNotifyRequest));
+ verify(mAutomaticBrightnessStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ }
+
+ @Test
+ public void getAutomaticBrightnessStrategy_getsAutomaticStrategy2IfRefactoringFlagIsNotSet() {
+ assertEquals(mAutomaticBrightnessStrategy2,
+ mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy());
+ }
+
+ @Test
+ public void getAutomaticBrightnessStrategy_getsAutomaticStrategyIfRefactoringFlagIsSet() {
+ when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
+ mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+ mInjector, DISPLAY_ID, mDisplayManagerFlags);
+ assertEquals(mAutomaticBrightnessStrategy,
+ mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy());
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
new file mode 100644
index 000000000000..fd43720b1c03
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
@@ -0,0 +1,415 @@
+/*
+ * 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.display.brightness.strategy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.view.Display;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AutomaticBrightnessStrategy2Test {
+ private static final int DISPLAY_ID = 0;
+ @Rule
+ public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+ @Mock
+ private AutomaticBrightnessController mAutomaticBrightnessController;
+
+ private BrightnessConfiguration mBrightnessConfiguration;
+ private float mDefaultScreenAutoBrightnessAdjustment;
+ private Context mContext;
+ private AutomaticBrightnessStrategy2 mAutomaticBrightnessStrategy;
+
+ @Before
+ public void before() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+ when(mContext.getContentResolver()).thenReturn(resolver);
+ mDefaultScreenAutoBrightnessAdjustment = Settings.System.getFloat(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Float.NaN);
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.5f);
+ mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy2(mContext, DISPLAY_ID);
+
+ mBrightnessConfiguration = new BrightnessConfiguration.Builder(
+ new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
+ when(mAutomaticBrightnessController.hasUserDataPoints()).thenReturn(true);
+ mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+ mAutomaticBrightnessController);
+ mAutomaticBrightnessStrategy.setBrightnessConfiguration(mBrightnessConfiguration,
+ true);
+ }
+
+ @After
+ public void after() {
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, mDefaultScreenAutoBrightnessAdjustment);
+ }
+
+ @Test
+ public void testAutoBrightnessState_AutoBrightnessDisabled() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(false);
+ int targetDisplayState = Display.STATE_ON;
+ boolean allowAutoBrightnessWhileDozing = false;
+ int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+ userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, /* adjustment */ 0.5f,
+ /* userChangedAutoBrightnessAdjustment= */ false, policy,
+ targetDisplayState, /* shouldResetShortTermModel */ true);
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+ }
+
+ @Test
+ public void testAutoBrightnessState_DisplayIsOff() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ int targetDisplayState = Display.STATE_OFF;
+ boolean allowAutoBrightnessWhileDozing = false;
+ int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+ userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, /* adjustment */ 0.5f,
+ /* userChangedAutoBrightnessAdjustment= */ false, policy,
+ targetDisplayState, /* shouldResetShortTermModel */ true);
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+ assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+ }
+
+ @Test
+ public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesNotAllow() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ int targetDisplayState = Display.STATE_DOZE;
+ boolean allowAutoBrightnessWhileDozing = false;
+ int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+ userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, /* adjustment */ 0.5f,
+ /* userChangedAutoBrightnessAdjustment= */ false, policy,
+ targetDisplayState, /* shouldResetShortTermModel */ true);
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+ assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+ }
+
+ @Test
+ public void testAutoBrightnessState_BrightnessReasonIsOverride() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ int targetDisplayState = Display.STATE_ON;
+ boolean allowAutoBrightnessWhileDozing = false;
+ int brightnessReason = BrightnessReason.REASON_OVERRIDE;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+ userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, /* adjustment */ 0.5f,
+ /* userChangedAutoBrightnessAdjustment= */ false, policy,
+ targetDisplayState, /* shouldResetShortTermModel */ true);
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+ }
+
+ @Test
+ public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ int targetDisplayState = Display.STATE_DOZE;
+ boolean allowAutoBrightnessWhileDozing = true;
+ int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+ userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, /* adjustment */ 0.4f,
+ /* userChangedAutoBrightnessAdjustment= */ true, policy,
+ targetDisplayState, /* shouldResetShortTermModel */ true);
+ assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+ }
+
+ @Test
+ public void testAutoBrightnessState_DisplayIsOn() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ int targetDisplayState = Display.STATE_ON;
+ boolean allowAutoBrightnessWhileDozing = false;
+ int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ float pendingBrightnessAdjustment = 0.1f;
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+ userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, pendingBrightnessAdjustment,
+ /* userChangedAutoBrightnessAdjustment= */ true, policy, targetDisplayState,
+ /* shouldResetShortTermModel */ true);
+ assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+ }
+
+ @Test
+ public void accommodateUserBrightnessChangesWorksAsExpected() {
+ // Verify the state if automaticBrightnessController is configured.
+ assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
+ boolean userSetBrightnessChanged = true;
+ float lastUserSetScreenBrightness = 0.2f;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ int targetDisplayState = Display.STATE_ON;
+ BrightnessConfiguration brightnessConfiguration = new BrightnessConfiguration.Builder(
+ new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
+ int autoBrightnessState = AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+ float temporaryAutoBrightnessAdjustments = 0.4f;
+ mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
+ setTemporaryAutoBrightnessAdjustment(temporaryAutoBrightnessAdjustments);
+ mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
+ lastUserSetScreenBrightness, policy, targetDisplayState, brightnessConfiguration,
+ autoBrightnessState);
+ verify(mAutomaticBrightnessController).configure(autoBrightnessState,
+ brightnessConfiguration,
+ lastUserSetScreenBrightness,
+ userSetBrightnessChanged, temporaryAutoBrightnessAdjustments,
+ /* userChangedAutoBrightnessAdjustment= */ false, policy, targetDisplayState,
+ /* shouldResetShortTermModel= */ true);
+ assertTrue(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
+ assertFalse(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
+ assertTrue(mAutomaticBrightnessStrategy.isShortTermModelActive());
+ // Verify the state when automaticBrightnessController is not configured
+ setTemporaryAutoBrightnessAdjustment(Float.NaN);
+ mAutomaticBrightnessStrategy.setAutomaticBrightnessController(null);
+ mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
+ mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
+ lastUserSetScreenBrightness, policy, targetDisplayState, brightnessConfiguration,
+ autoBrightnessState);
+ assertFalse(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
+ assertTrue(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
+ assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
+ }
+
+ @Test
+ public void adjustAutomaticBrightnessStateIfValid() throws Settings.SettingNotFoundException {
+ float brightnessState = 0.3f;
+ float autoBrightnessAdjustment = 0.2f;
+ when(mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()).thenReturn(
+ autoBrightnessAdjustment);
+ mAutomaticBrightnessStrategy.adjustAutomaticBrightnessStateIfValid(brightnessState);
+ assertEquals(autoBrightnessAdjustment,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+ assertEquals(autoBrightnessAdjustment, Settings.System.getFloatForUser(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+ UserHandle.USER_CURRENT), 0.0f);
+ assertEquals(BrightnessReason.ADJUSTMENT_AUTO,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
+ float invalidBrightness = -0.5f;
+ mAutomaticBrightnessStrategy
+ .adjustAutomaticBrightnessStateIfValid(invalidBrightness);
+ assertEquals(autoBrightnessAdjustment,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+ assertEquals(0,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
+ }
+
+ @Test
+ public void updatePendingAutoBrightnessAdjustments() {
+ // Verify the state when the pendingAutoBrightnessAdjustments are not present
+ setPendingAutoBrightnessAdjustment(Float.NaN);
+ assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+ assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+ // Verify the state when the pendingAutoBrightnessAdjustments are present, but
+ // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are the same
+ float autoBrightnessAdjustment = 0.3f;
+ setPendingAutoBrightnessAdjustment(autoBrightnessAdjustment);
+ setAutoBrightnessAdjustment(autoBrightnessAdjustment);
+ assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+ assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+ assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
+ 0.0f);
+ // Verify the state when the pendingAutoBrightnessAdjustments are present, and
+ // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are not the same
+ float pendingAutoBrightnessAdjustment = 0.2f;
+ setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustment);
+ setTemporaryAutoBrightnessAdjustment(0.1f);
+ assertTrue(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+ assertTrue(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+ assertEquals(pendingAutoBrightnessAdjustment,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+ assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
+ 0.0f);
+ assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(),
+ 0.0f);
+ }
+
+ @Test
+ public void setAutomaticBrightnessWorksAsExpected() {
+ float automaticScreenBrightness = 0.3f;
+ AutomaticBrightnessController automaticBrightnessController = mock(
+ AutomaticBrightnessController.class);
+ when(automaticBrightnessController.getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+ .thenReturn(automaticScreenBrightness);
+ when(automaticBrightnessController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+ any(BrightnessEvent.class)))
+ .thenReturn(automaticScreenBrightness);
+ mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+ automaticBrightnessController);
+ assertEquals(automaticScreenBrightness,
+ mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
+ new BrightnessEvent(DISPLAY_ID)), 0.0f);
+ assertEquals(automaticScreenBrightness,
+ mAutomaticBrightnessStrategy.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+ new BrightnessEvent(DISPLAY_ID)), 0.0f);
+ }
+
+ @Test
+ public void shouldUseAutoBrightness() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ assertTrue(mAutomaticBrightnessStrategy.shouldUseAutoBrightness());
+ }
+
+ @Test
+ public void setPendingAutoBrightnessAdjustments() throws Settings.SettingNotFoundException {
+ float pendingAutoBrightnessAdjustments = 0.3f;
+ setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustments);
+ assertEquals(pendingAutoBrightnessAdjustments,
+ mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(), 0.0f);
+ assertEquals(pendingAutoBrightnessAdjustments, Settings.System.getFloatForUser(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+ UserHandle.USER_CURRENT), 0.0f);
+ }
+
+ @Test
+ public void setTemporaryAutoBrightnessAdjustment() {
+ float temporaryAutoBrightnessAdjustment = 0.3f;
+ mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
+ temporaryAutoBrightnessAdjustment);
+ assertEquals(temporaryAutoBrightnessAdjustment,
+ mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(), 0.0f);
+ }
+
+ @Test
+ public void setAutoBrightnessApplied() {
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
+ assertTrue(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
+ }
+
+ @Test
+ public void testVerifyNoAutoBrightnessAdjustmentsArePopulatedForNonDefaultDisplay() {
+ int newDisplayId = 1;
+ mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy2(mContext, newDisplayId);
+ mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(0.3f);
+ assertEquals(0.5f, mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(),
+ 0.0f);
+ }
+
+ private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+ }
+
+ private void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
+ mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
+ temporaryAutoBrightnessAdjustment);
+ }
+
+ private void setAutoBrightnessAdjustment(float autoBrightnessAdjustment) {
+ mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(autoBrightnessAdjustment);
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index 4e55270b1d6b..6e163ca69c13 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -398,6 +398,34 @@ public class AutomaticBrightnessStrategyTest {
0.0f);
}
+ @Test
+ public void isAutoBrightnessValid_returnsFalseWhenAutoBrightnessIsDisabled() {
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessValid());
+ }
+
+ @Test
+ public void isAutoBrightnessValid_returnsFalseWhenBrightnessIsInvalid() {
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON, true,
+ BrightnessReason.REASON_UNKNOWN,
+ DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT, 0.1f,
+ false);
+ when(mAutomaticBrightnessController.getAutomaticScreenBrightness(null))
+ .thenReturn(Float.NaN);
+ assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessValid());
+ }
+
+ @Test
+ public void isAutoBrightnessValid_returnsTrueWhenBrightnessIsValid() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON, true,
+ BrightnessReason.REASON_UNKNOWN,
+ DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT, 0.1f,
+ false);
+ when(mAutomaticBrightnessController.getAutomaticScreenBrightness(null))
+ .thenReturn(0.2f);
+ assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessValid());
+ }
+
private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
Settings.System.putFloat(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
index b182ccef091e..0cf0850f0882 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
@@ -19,6 +19,7 @@ package com.android.server.display.mode
import android.content.Context
import android.content.ContextWrapper
import android.hardware.display.BrightnessInfo
+import android.util.SparseBooleanArray
import android.view.Display
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
@@ -62,8 +63,11 @@ class BrightnessObserverTest {
whenever(mockFlags.isVsyncLowLightVoteEnabled).thenReturn(testCase.vsyncLowLightVoteEnabled)
val displayModeDirector = DisplayModeDirector(
spyContext, testHandler, mockInjector, mockFlags)
+ val vrrByDisplay = SparseBooleanArray()
+ vrrByDisplay.put(Display.DEFAULT_DISPLAY, testCase.vrrSupported)
+ displayModeDirector.injectVrrByDisplay(vrrByDisplay)
val brightnessObserver = displayModeDirector.BrightnessObserver(
- spyContext, testHandler, mockInjector, testCase.vrrSupported, mockFlags)
+ spyContext, testHandler, mockInjector, mockFlags)
brightnessObserver.onRefreshRateSettingChangedLocked(0.0f, 120.0f)
brightnessObserver.updateBlockingZoneThresholds(mockDeviceConfig, false)
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index fbc38a24b8a5..0efd04657033 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -338,8 +338,6 @@ public class DisplayModeDirectorTest {
.thenReturn(false);
when(resources.getBoolean(R.bool.config_refreshRateSynchronizationEnabled))
.thenReturn(false);
- when(resources.getBoolean(R.bool.config_supportsDvrr))
- .thenReturn(false);
when(resources.getInteger(R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
.thenReturn(10000);
when(resources.getInteger(R.integer.config_defaultPeakRefreshRate))
@@ -393,41 +391,87 @@ public class DisplayModeDirectorTest {
private DisplayModeDirector createDirectorFromRefreshRateArray(
float[] refreshRates, int baseModeId, float defaultRefreshRate) {
- Display.Mode[] modes = new Display.Mode[refreshRates.length];
- Display.Mode defaultMode = null;
- for (int i = 0; i < refreshRates.length; i++) {
- modes[i] = new Display.Mode(
- /*modeId=*/baseModeId + i, /*width=*/1000, /*height=*/1000, refreshRates[i]);
- if (refreshRates[i] == defaultRefreshRate) {
- defaultMode = modes[i];
- }
- }
+ return createDirectorFromRefreshRateArray(refreshRates, baseModeId, defaultRefreshRate,
+ new int[]{DISPLAY_ID});
+ }
+
+ private DisplayModeDirector createDirectorFromRefreshRateArray(
+ float[] refreshRates, int baseModeId, float defaultRefreshRate, int[] displayIds) {
+ Display.Mode[] modes = createDisplayModes(refreshRates, baseModeId);
+ Display.Mode defaultMode = getDefaultMode(modes, defaultRefreshRate);
+
assertThat(defaultMode).isNotNull();
- return createDirectorFromModeArray(modes, defaultMode);
+ return createDirectorFromModeArray(modes, defaultMode, displayIds);
}
private DisplayModeDirector createDirectorFromModeArray(Display.Mode[] modes,
Display.Mode defaultMode) {
+ return createDirectorFromModeArray(modes, defaultMode, new int[]{DISPLAY_ID});
+ }
+
+ private DisplayModeDirector createDirectorFromModeArray(Display.Mode[] modes,
+ Display.Mode defaultMode, int[] displayIds) {
DisplayModeDirector director =
new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
director.setLoggingEnabled(true);
+ setupModesForDisplays(director, displayIds , modes, defaultMode);
+ return director;
+ }
+
+ private DisplayModeDirector createDirectorFromFpsRange(int minFps, int maxFps) {
+ return createDirectorFromRefreshRateArray(
+ createRefreshRateRanges(minFps, maxFps),
+ /*baseModeId=*/minFps,
+ /*defaultRefreshRate=*/minFps,
+ new int[]{DISPLAY_ID});
+ }
+
+ private DisplayModeDirector createDirectorFromFpsRange(
+ int minFps, int maxFps, int[] displayIds) {
+ return createDirectorFromRefreshRateArray(
+ createRefreshRateRanges(minFps, maxFps),
+ /*baseModeId=*/minFps,
+ /*defaultRefreshRate=*/minFps,
+ displayIds);
+ }
+
+ private void setupModesForDisplays(DisplayModeDirector director, int[] displayIds,
+ Display.Mode[] modes, Display.Mode defaultMode) {
SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
- supportedModesByDisplay.put(DISPLAY_ID, modes);
- director.injectSupportedModesByDisplay(supportedModesByDisplay);
SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>();
- defaultModesByDisplay.put(DISPLAY_ID, defaultMode);
+ for (int displayId: displayIds) {
+ supportedModesByDisplay.put(displayId, modes);
+ defaultModesByDisplay.put(displayId, defaultMode);
+ }
+ director.injectSupportedModesByDisplay(supportedModesByDisplay);
director.injectDefaultModeByDisplay(defaultModesByDisplay);
- return director;
}
- private DisplayModeDirector createDirectorFromFpsRange(int minFps, int maxFps) {
+ private Display.Mode[] createDisplayModes(float[] refreshRates, int baseModeId) {
+ Display.Mode[] modes = new Display.Mode[refreshRates.length];
+ for (int i = 0; i < refreshRates.length; i++) {
+ modes[i] = new Display.Mode(
+ /*modeId=*/baseModeId + i, /*width=*/1000, /*height=*/1000, refreshRates[i]);
+ }
+ return modes;
+ }
+
+ private Display.Mode getDefaultMode(Display.Mode[] modes, float defaultRefreshRate) {
+ for (Display.Mode mode : modes) {
+ if (mode.getRefreshRate() == defaultRefreshRate) {
+ return mode;
+ }
+ }
+ return null;
+ }
+
+ private float[] createRefreshRateRanges(int minFps, int maxFps) {
int numRefreshRates = maxFps - minFps + 1;
float[] refreshRates = new float[numRefreshRates];
for (int i = 0; i < numRefreshRates; i++) {
refreshRates[i] = minFps + i;
}
- return createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/minFps,
- /*defaultRefreshRate=*/minFps);
+ return refreshRates;
}
@Test
@@ -1893,6 +1937,7 @@ public class DisplayModeDirectorTest {
mInjector.mDisplayInfo.displayId = DISPLAY_ID_2;
DisplayModeDirector director = createDirectorFromModeArray(TEST_MODES, DEFAULT_MODE_60);
+ director.start(createMockSensorManager());
SparseArray<Vote> votes = new SparseArray<>();
votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 50f));
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
index 92016dfc631b..d0dd9218eb17 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -39,6 +39,7 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.Looper;
import android.provider.DeviceConfigInterface;
@@ -426,8 +427,12 @@ public class DisplayObserverTest {
return true;
}).when(mInjector).getDisplayInfo(eq(EXTERNAL_DISPLAY), /*displayInfo=*/ any());
- doAnswer(c -> mock(SensorManagerInternal.class)).when(mInjector).getSensorManagerInternal();
+ doAnswer(c -> mock(SensorManagerInternal.class))
+ .when(mInjector).getSensorManagerInternal();
doAnswer(c -> mock(DeviceConfigInterface.class)).when(mInjector).getDeviceConfig();
+ doAnswer(c -> mock(DisplayManagerInternal.class))
+ .when(mInjector).getDisplayManagerInternal();
+
mDefaultDisplay = mock(Display.class);
when(mDefaultDisplay.getDisplayId()).thenReturn(DEFAULT_DISPLAY);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
index 230317ba738b..196a20231fbb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
@@ -19,6 +19,8 @@ package com.android.server.display.mode
import android.content.Context
import android.content.ContextWrapper
import android.provider.Settings
+import android.util.SparseBooleanArray
+import android.view.Display
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
import com.android.internal.util.test.FakeSettingsProvider
@@ -39,7 +41,6 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(TestParameterInjector::class)
class SettingsObserverTest {
-
@get:Rule
val mockitoRule = MockitoJUnit.rule()
@@ -68,8 +69,11 @@ class SettingsObserverTest {
val displayModeDirector = DisplayModeDirector(
spyContext, testHandler, mockInjector, mockFlags)
+ val vrrByDisplay = SparseBooleanArray()
+ vrrByDisplay.put(Display.DEFAULT_DISPLAY, testCase.vrrSupported)
+ displayModeDirector.injectVrrByDisplay(vrrByDisplay)
val settingsObserver = displayModeDirector.SettingsObserver(
- spyContext, testHandler, testCase.dvrrSupported, mockFlags)
+ spyContext, testHandler, mockFlags)
settingsObserver.onChange(
false, Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE), 1)
@@ -79,7 +83,7 @@ class SettingsObserverTest {
}
enum class SettingsObserverTestCase(
- val dvrrSupported: Boolean,
+ val vrrSupported: Boolean,
val vsyncLowPowerVoteEnabled: Boolean,
val lowPowerModeEnabled: Boolean,
internal val expectedVote: Vote?
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 a7430e563904..419bcb8650a7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -38,6 +38,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.am.ActivityManagerInternalTest.CustomThread;
import static com.android.server.am.ActivityManagerService.Injector;
@@ -692,6 +693,31 @@ public class ActivityManagerServiceTest {
assertEquals(uid, -1);
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testFifoSwitch() {
+ addUidRecord(TEST_UID, TEST_PACKAGE);
+ final ProcessRecord fifoProc = mAms.getProcessRecordLocked(TEST_PACKAGE, TEST_UID);
+ final var wpc = fifoProc.getWindowProcessController();
+ spyOn(wpc);
+ doReturn(true).when(wpc).useFifoUiScheduling();
+ fifoProc.makeActive(fifoProc.getThread(), mAms.mProcessStats);
+ assertTrue(fifoProc.useFifoUiScheduling());
+ assertTrue(mAms.mSpecifiedFifoProcesses.contains(fifoProc));
+
+ // If there is a request to use more CPU resource (e.g. camera), the current fifo process
+ // should switch the capability of using fifo.
+ final UidRecord uidRecord = addUidRecord(TEST_UID + 1, TEST_PACKAGE + 1);
+ uidRecord.setCurProcState(PROCESS_STATE_TOP);
+ mAms.adjustFifoProcessesIfNeeded(uidRecord.getUid(), false /* allowSpecifiedFifo */);
+ assertFalse(fifoProc.useFifoUiScheduling());
+ mAms.adjustFifoProcessesIfNeeded(uidRecord.getUid(), true /* allowSpecifiedFifo */);
+ assertTrue(fifoProc.useFifoUiScheduling());
+
+ fifoProc.makeInactive(mAms.mProcessStats);
+ assertFalse(mAms.mSpecifiedFifoProcesses.contains(fifoProc));
+ }
+
@Test
public void testGlobalIsolatedUidAllocator() {
final IsolatedUidRange globalUidRange = mAms.mProcessList.mGlobalIsolatedUids;
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index b203cf640097..c4a042370de5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -38,7 +38,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.Manifest;
-import android.annotation.UserIdInt;
import android.app.backup.BackupManager;
import android.app.backup.ISelectBackupTransportCallback;
import android.app.job.JobScheduler;
@@ -59,10 +58,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.DumpUtils;
import com.android.server.SystemService;
import com.android.server.backup.utils.RandomAccessFileUtils;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -74,6 +75,7 @@ import org.mockito.quality.Strictness;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.io.Writer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -85,8 +87,6 @@ public class BackupManagerServiceTest {
"class");
private static final int NON_SYSTEM_USER = UserHandle.USER_SYSTEM + 1;
- @UserIdInt
- private int mUserId;
@Mock
private UserBackupManagerService mSystemUserBackupManagerService;
@Mock
@@ -94,8 +94,6 @@ public class BackupManagerServiceTest {
@Mock
private Context mContextMock;
@Mock
- private PrintWriter mPrintWriterMock;
- @Mock
private UserManager mUserManagerMock;
@Mock
private UserInfo mUserInfoMock;
@@ -104,6 +102,7 @@ public class BackupManagerServiceTest {
private BackupManagerServiceTestable mService;
private BackupManagerService.Lifecycle mServiceLifecycle;
+ private FakePrintWriter mFakePrintWriter;
private static File sTestDir;
private MockitoSession mSession;
@@ -114,6 +113,7 @@ public class BackupManagerServiceTest {
this)
.strictness(Strictness.LENIENT)
.spyStatic(UserBackupManagerService.class)
+ .spyStatic(DumpUtils.class)
.startMocking();
doReturn(mSystemUserBackupManagerService).when(
() -> UserBackupManagerService.createAndInitializeService(
@@ -122,8 +122,7 @@ public class BackupManagerServiceTest {
() -> UserBackupManagerService.createAndInitializeService(eq(NON_SYSTEM_USER),
any(), any(), any()));
- mUserId = UserHandle.USER_SYSTEM;
-
+ when(mNonSystemUserBackupManagerService.getUserId()).thenReturn(NON_SYSTEM_USER);
when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock);
when(mUserManagerMock.getUserInfo(NON_SYSTEM_USER)).thenReturn(mUserInfoMock);
// Null main user means there is no main user on the device.
@@ -139,6 +138,8 @@ public class BackupManagerServiceTest {
when(mContextMock.getSystemService(Context.JOB_SCHEDULER_SERVICE))
.thenReturn(mock(JobScheduler.class));
+
+ mFakePrintWriter = new FakePrintWriter();
}
@After
@@ -552,7 +553,8 @@ public class BackupManagerServiceTest {
}
};
- mService.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, listener);
+ mService.selectBackupTransportAsyncForUser(
+ UserHandle.USER_SYSTEM, TRANSPORT_COMPONENT_NAME, listener);
assertEquals(BackupManager.ERROR_BACKUP_NOT_ALLOWED, (int) future.get(5, TimeUnit.SECONDS));
}
@@ -560,9 +562,10 @@ public class BackupManagerServiceTest {
@Test
public void selectBackupTransportAsyncForUser_beforeUserUnlockedWithNullListener_doesNotThrow()
throws Exception {
- createBackupManagerServiceAndUnlockSystemUser();
+ mService = new BackupManagerServiceTestable(mContextMock);
- mService.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, null);
+ mService.selectBackupTransportAsyncForUser(
+ UserHandle.USER_SYSTEM, TRANSPORT_COMPONENT_NAME, null);
// No crash.
}
@@ -570,13 +573,11 @@ public class BackupManagerServiceTest {
@Test
public void selectBackupTransportAsyncForUser_beforeUserUnlockedListenerThrowing_doesNotThrow()
throws Exception {
- createBackupManagerServiceAndUnlockSystemUser();
-
+ mService = new BackupManagerServiceTestable(mContextMock);
ISelectBackupTransportCallback.Stub listener =
new ISelectBackupTransportCallback.Stub() {
@Override
- public void onSuccess(String transportName) {
- }
+ public void onSuccess(String transportName) {}
@Override
public void onFailure(int reason) throws RemoteException {
@@ -584,55 +585,91 @@ public class BackupManagerServiceTest {
}
};
- mService.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, listener);
+ mService.selectBackupTransportAsyncForUser(
+ UserHandle.USER_SYSTEM, TRANSPORT_COMPONENT_NAME, listener);
// No crash.
}
@Test
- public void dump_callerDoesNotHaveDumpPermission_ignored() {
+ public void dump_callerDoesNotHaveDumpOrUsageStatsPermission_ignored() {
+ mockDumpPermissionsGranted(false);
createBackupManagerServiceAndUnlockSystemUser();
- when(mContextMock.checkCallingOrSelfPermission(
- Manifest.permission.DUMP)).thenReturn(
- PackageManager.PERMISSION_DENIED);
+ when(mContextMock.checkCallingOrSelfPermission(Manifest.permission.DUMP))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
- mService.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]);
+ mService.dump(mFileDescriptorStub, mFakePrintWriter, new String[0]);
verify(mSystemUserBackupManagerService, never()).dump(any(), any(), any());
verify(mNonSystemUserBackupManagerService, never()).dump(any(), any(), any());
}
@Test
- public void dump_callerDoesNotHavePackageUsageStatsPermission_ignored() {
+ public void dump_forOneUser_callerDoesNotHaveInteractAcrossUsersFullPermission_ignored() {
+ mockDumpPermissionsGranted(true);
createBackupManagerServiceAndUnlockSystemUser();
- when(mContextMock.checkCallingOrSelfPermission(
- Manifest.permission.PACKAGE_USAGE_STATS)).thenReturn(
- PackageManager.PERMISSION_DENIED);
+ mService.setBackupServiceActive(NON_SYSTEM_USER, true);
+ simulateUserUnlocked(NON_SYSTEM_USER);
+ doThrow(new SecurityException())
+ .when(mContextMock)
+ .enforceCallingOrSelfPermission(
+ eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyString());
- mService.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]);
+ String[] args = new String[] {"--user", Integer.toString(NON_SYSTEM_USER)};
+ Assert.assertThrows(
+ SecurityException.class,
+ () -> mService.dump(mFileDescriptorStub, mFakePrintWriter, args));
+ verify(mNonSystemUserBackupManagerService, never()).dump(any(), any(), any());
+ }
+
+ @Test
+ public void dump_forOneUser_callerHasInteractAcrossUsersFullPermission_dumpsSpecifiedUser() {
+ mockDumpPermissionsGranted(true);
+ createBackupManagerServiceAndUnlockSystemUser();
+ mService.setBackupServiceActive(NON_SYSTEM_USER, true);
+ simulateUserUnlocked(NON_SYSTEM_USER);
+
+ String[] args = new String[] {"--user", Integer.toString(UserHandle.USER_SYSTEM)};
+ mService.dump(mFileDescriptorStub, mFakePrintWriter, args);
+
+ verify(mSystemUserBackupManagerService).dump(any(), any(), any());
+ }
+
+ @Test
+ public void dump_users_callerHasInteractAcrossUsersFullPermission_dumpsUsers() {
+ mockDumpPermissionsGranted(true);
+ createBackupManagerServiceAndUnlockSystemUser();
+ mService.setBackupServiceActive(NON_SYSTEM_USER, true);
+ simulateUserUnlocked(NON_SYSTEM_USER);
+
+ String[] args = new String[] {"users"};
+ mService.dump(mFileDescriptorStub, mFakePrintWriter, args);
+
+ // Check that dump() invocations are not called on user's Backup service,
+ // as 'dumpsys backup users' only list users for whom Backup service is running.
verify(mSystemUserBackupManagerService, never()).dump(any(), any(), any());
verify(mNonSystemUserBackupManagerService, never()).dump(any(), any(), any());
+ assertThat(mFakePrintWriter.mPrintedSoFar)
+ .isEqualTo("Backup Manager is running for users: 0 1");
}
- /**
- * Test that {@link BackupManagerService#dump(FileDescriptor, PrintWriter, String[])} dumps
- * system user information before non-system user information.
- */
@Test
- public void testDump_systemUserFirst() {
+ public void dump_allUsers_dumpsSystemUserFirst() {
+ mockDumpPermissionsGranted(true);
createBackupManagerServiceAndUnlockSystemUser();
mService.setBackupServiceActive(NON_SYSTEM_USER, true);
simulateUserUnlocked(NON_SYSTEM_USER);
+
String[] args = new String[0];
- mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock, args);
+ mService.dump(mFileDescriptorStub, mFakePrintWriter, args);
InOrder inOrder =
inOrder(mSystemUserBackupManagerService, mNonSystemUserBackupManagerService);
inOrder.verify(mSystemUserBackupManagerService)
- .dump(mFileDescriptorStub, mPrintWriterMock, args);
+ .dump(mFileDescriptorStub, mFakePrintWriter, args);
inOrder.verify(mNonSystemUserBackupManagerService)
- .dump(mFileDescriptorStub, mPrintWriterMock, args);
+ .dump(mFileDescriptorStub, mFakePrintWriter, args);
inOrder.verifyNoMoreInteractions();
}
@@ -753,6 +790,11 @@ public class BackupManagerServiceTest {
return new File(sTestDir, "rememberActivated-" + userId);
}
+ private static void mockDumpPermissionsGranted(boolean granted) {
+ doReturn(granted)
+ .when(() -> DumpUtils.checkDumpAndUsageStatsPermission(any(), any(), any()));
+ }
+
private static class BackupManagerServiceTestable extends BackupManagerService {
static boolean sBackupDisabled = false;
static int sCallingUserId = -1;
@@ -803,4 +845,17 @@ public class BackupManagerServiceTest {
runnable.run();
}
}
+
+ private static class FakePrintWriter extends PrintWriter {
+ String mPrintedSoFar = "";
+
+ FakePrintWriter() {
+ super(Writer.nullWriter());
+ }
+
+ @Override
+ public void print(String s) {
+ mPrintedSoFar = mPrintedSoFar + s;
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
index 18dc114a8cd1..7e179095d99b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
@@ -18,6 +18,8 @@ package com.android.server.backup;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
import android.annotation.NonNull;
import android.app.backup.BackupHelper;
import android.app.backup.BackupHelperWithLogger;
@@ -29,8 +31,6 @@ import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
-import static org.mockito.Mockito.when;
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -92,7 +92,8 @@ public class SystemBackupAgentTest {
"people",
"app_locales",
"app_gender",
- "companion");
+ "companion",
+ "system_gender");
}
@Test
@@ -116,7 +117,8 @@ public class SystemBackupAgentTest {
"people",
"app_locales",
"app_gender",
- "companion");
+ "companion",
+ "system_gender");
}
@Test
@@ -132,7 +134,9 @@ public class SystemBackupAgentTest {
"notifications",
"permissions",
"app_locales",
- "companion");
+ "companion",
+ "app_gender",
+ "system_gender");
}
@Test
@@ -152,7 +156,9 @@ public class SystemBackupAgentTest {
"account_manager",
"usage_stats",
"shortcut_manager",
- "companion");
+ "companion",
+ "app_gender",
+ "system_gender");
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 671472d619d7..6df4907af93c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -1978,7 +1978,7 @@ public class QuotaControllerTest {
}
@Test
- public void testIsWithinQuotaLocked_UnderDuration_OverJobCountRateLimitWindow() {
+ public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2021,7 +2021,7 @@ public class QuotaControllerTest {
}
@Test
- public void testIsWithinQuotaLocked_OverDuration_OverJobCountRateLimitWindow() {
+ public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2167,73 +2167,6 @@ public class QuotaControllerTest {
}
@Test
- public void testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow() {
- setDischarging();
-
- JobStatus jobRunning = createJobStatus(
- "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 1);
- JobStatus jobPending = createJobStatus(
- "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 2);
- setStandbyBucket(WORKING_INDEX, jobRunning, jobPending);
-
- setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10);
-
- long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 9), false);
-
- final ExecutionStats stats;
- synchronized (mQuotaController.mLock) {
- stats = mQuotaController.getExecutionStatsLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
- assertTrue(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(10, stats.jobCountLimit);
- assertEquals(9, stats.bgJobCountInWindow);
- }
-
- when(mJobSchedulerService.isCurrentlyRunningLocked(jobRunning)).thenReturn(true);
- when(mJobSchedulerService.isCurrentlyRunningLocked(jobPending)).thenReturn(false);
-
- InOrder inOrder = inOrder(mJobSchedulerService);
- trackJobs(jobRunning, jobPending);
- // UID in the background.
- setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
- // Start the job.
- synchronized (mQuotaController.mLock) {
- mQuotaController.prepareForExecutionLocked(jobRunning);
- }
-
- advanceElapsedClock(MINUTE_IN_MILLIS);
- // Wait for some extra time to allow for job processing.
- ArraySet<JobStatus> expected = new ArraySet<>();
- expected.add(jobPending);
- inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(eq(expected));
-
- synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController.isWithinQuotaLocked(jobRunning));
- assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
- assertTrue(jobRunning.isReady());
- assertFalse(mQuotaController.isWithinQuotaLocked(jobPending));
- assertFalse(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
- assertFalse(jobPending.isReady());
- assertEquals(10, stats.bgJobCountInWindow);
- }
-
- advanceElapsedClock(MINUTE_IN_MILLIS);
- synchronized (mQuotaController.mLock) {
- mQuotaController.maybeStopTrackingJobLocked(jobRunning, null);
- }
-
- synchronized (mQuotaController.mLock) {
- assertFalse(mQuotaController
- .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
- assertEquals(10, stats.bgJobCountInWindow);
- }
- }
-
- @Test
public void testIsWithinQuotaLocked_TimingSession() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -4718,7 +4651,7 @@ public class QuotaControllerTest {
// Handler is told to check when the quota will be consumed, not when the initial
// remaining time is over.
verify(handler, atLeast(1)).sendMessageDelayed(
- argThat(msg -> msg.what == QuotaController.MSG_REACHED_TIME_QUOTA),
+ argThat(msg -> msg.what == QuotaController.MSG_REACHED_QUOTA),
eq(10 * SECOND_IN_MILLIS));
verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
@@ -6685,7 +6618,7 @@ public class QuotaControllerTest {
// Handler is told to check when the quota will be consumed, not when the initial
// remaining time is over.
verify(handler, atLeast(1)).sendMessageDelayed(
- argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_TIME_QUOTA),
+ argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_QUOTA),
eq(10 * SECOND_IN_MILLIS));
verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 759a974bd41f..79f1574105ba 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -34,6 +34,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -58,6 +59,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.util.Log;
@@ -139,7 +141,8 @@ public final class UserManagerServiceTest {
.mockStatic(Settings.Secure.class)
.build();
- @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
+ SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
private final Object mPackagesLock = new Object();
private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
@@ -584,10 +587,12 @@ public final class UserManagerServiceTest {
public void testAutoLockPrivateProfile() {
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
+ int mainUser = mUms.getMainUserId();
+ assumeTrue(mUms.canAddPrivateProfile(mainUser));
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
- USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null);
Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(),
any());
@@ -604,10 +609,12 @@ public final class UserManagerServiceTest {
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ int mainUser = mUms.getMainUserId();
+ assumeTrue(mUms.canAddPrivateProfile(mainUser));
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
- USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null);
mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(),
@@ -625,6 +632,7 @@ public final class UserManagerServiceTest {
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ assumeTrue(mUms.canAddPrivateProfile(0));
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
@@ -644,10 +652,12 @@ public final class UserManagerServiceTest {
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ int mainUser = mUms.getMainUserId();
+ assumeTrue(mUms.canAddPrivateProfile(mainUser));
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
- USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null);
mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
@@ -664,13 +674,15 @@ public final class UserManagerServiceTest {
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ int mainUser = mUms.getMainUserId();
+ assumeTrue(mUms.canAddPrivateProfile(mainUser));
UserManagerService mSpiedUms = spy(mUms);
mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
when(mPowerManager.isInteractive()).thenReturn(false);
UserInfo privateProfileUser =
mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
- USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null);
Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
eq(privateProfileUser.getUserHandle().getIdentifier()), any(),
anyLong());
@@ -702,8 +714,10 @@ public final class UserManagerServiceTest {
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ int mainUser = mUms.getMainUserId();
+ assumeTrue(mUms.canAddPrivateProfile(mainUser));
mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
- USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null);
// Set the preference to auto lock on device lock
mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
@@ -754,6 +768,7 @@ public final class UserManagerServiceTest {
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_HIDING_PROFILES);
+ assumeTrue(mUms.canAddPrivateProfile(0));
UserInfo privateProfileUser =
mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
@@ -763,23 +778,23 @@ public final class UserManagerServiceTest {
}
@Test
+ @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
UserManagerService mSpiedUms = spy(mUms);
+ assumeTrue(mUms.isHeadlessSystemUserMode());
int mainUser = mSpiedUms.getMainUserId();
- doReturn(true).when(mSpiedUms).isHeadlessSystemUserMode();
- assertThat(mSpiedUms.canAddPrivateProfile(mainUser)).isTrue();
+ // Check whether private space creation is blocked on the device
+ assumeTrue(mSpiedUms.canAddPrivateProfile(mainUser));
assertThat(mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(
PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)).isNotNull();
}
@Test
+ @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testCreatePrivateProfileOnSecondaryUser_shouldNotAllowCreation() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+ assumeTrue(mUms.canAddMoreUsersOfType(USER_TYPE_FULL_SECONDARY));
UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
assertThat(mUms.canAddPrivateProfile(user.id)).isFalse();
assertThrows(ServiceSpecificException.class,
@@ -788,52 +803,48 @@ public final class UserManagerServiceTest {
}
@Test
+ @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testCreatePrivateProfileOnAutoDevices_shouldNotAllowCreation() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_AUTOMOTIVE), anyInt());
int mainUser = mUms.getMainUserId();
- assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+ assertThat(mUms.canAddPrivateProfile(mainUser)).isFalse();
assertThrows(ServiceSpecificException.class,
() -> mUms.createProfileForUserWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
}
@Test
+ @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testCreatePrivateProfileOnTV_shouldNotAllowCreation() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_LEANBACK), anyInt());
int mainUser = mUms.getMainUserId();
- assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+ assertThat(mUms.canAddPrivateProfile(mainUser)).isFalse();
assertThrows(ServiceSpecificException.class,
() -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
}
@Test
+ @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testCreatePrivateProfileOnEmbedded_shouldNotAllowCreation() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_EMBEDDED), anyInt());
int mainUser = mUms.getMainUserId();
- assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+ assertThat(mUms.canAddPrivateProfile(mainUser)).isFalse();
assertThrows(ServiceSpecificException.class,
() -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
}
@Test
+ @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testCreatePrivateProfileOnWatch_shouldNotAllowCreation() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
- mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_WATCH), anyInt());
int mainUser = mUms.getMainUserId();
- assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+ assertThat(mUms.canAddPrivateProfile(mainUser)).isFalse();
assertThrows(ServiceSpecificException.class,
() -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index 51c9d0ae5e5d..f2b4136c51ed 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -4,58 +4,6 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-filegroup {
- name: "power_stats_ravenwood_tests",
- srcs: [
- "src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java",
- "src/com/android/server/power/stats/AggregatedPowerStatsTest.java",
- "src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java",
- "src/com/android/server/power/stats/AudioPowerCalculatorTest.java",
- "src/com/android/server/power/stats/BatteryChargeCalculatorTest.java",
- "src/com/android/server/power/stats/BatteryStatsCounterTest.java",
- "src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java",
- "src/com/android/server/power/stats/BatteryStatsDualTimerTest.java",
- "src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java",
- "src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java",
- "src/com/android/server/power/stats/BatteryStatsHistoryTest.java",
- "src/com/android/server/power/stats/BatteryStatsImplTest.java",
- "src/com/android/server/power/stats/BatteryStatsNoteTest.java",
- "src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java",
- "src/com/android/server/power/stats/BatteryStatsSensorTest.java",
- "src/com/android/server/power/stats/BatteryStatsServTest.java",
- "src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java",
- "src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java",
- "src/com/android/server/power/stats/BatteryStatsTimerTest.java",
- "src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java",
- "src/com/android/server/power/stats/BatteryUsageStatsTest.java",
- "src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java",
- "src/com/android/server/power/stats/CameraPowerCalculatorTest.java",
- "src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java",
- "src/com/android/server/power/stats/CpuPowerCalculatorTest.java",
- "src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java",
- "src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java",
- "src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java",
- "src/com/android/server/power/stats/GnssPowerCalculatorTest.java",
- "src/com/android/server/power/stats/IdlePowerCalculatorTest.java",
- "src/com/android/server/power/stats/LongSamplingCounterArrayTest.java",
- "src/com/android/server/power/stats/LongSamplingCounterTest.java",
- "src/com/android/server/power/stats/MemoryPowerCalculatorTest.java",
- "src/com/android/server/power/stats/MultiStateStatsTest.java",
- "src/com/android/server/power/stats/PowerStatsAggregatorTest.java",
- "src/com/android/server/power/stats/PowerStatsCollectorTest.java",
- "src/com/android/server/power/stats/PowerStatsExporterTest.java",
- "src/com/android/server/power/stats/PowerStatsSchedulerTest.java",
- "src/com/android/server/power/stats/PowerStatsStoreTest.java",
- "src/com/android/server/power/stats/PowerStatsUidResolverTest.java",
- "src/com/android/server/power/stats/ScreenPowerCalculatorTest.java",
- "src/com/android/server/power/stats/SensorPowerCalculatorTest.java",
- "src/com/android/server/power/stats/UserPowerCalculatorTest.java",
- "src/com/android/server/power/stats/VideoPowerCalculatorTest.java",
- "src/com/android/server/power/stats/WakelockPowerCalculatorTest.java",
- "src/com/android/server/power/stats/WifiPowerCalculatorTest.java",
- ],
-}
-
android_test {
name: "PowerStatsTests",
@@ -79,7 +27,6 @@ android_test {
"servicestests-utils",
"platform-test-annotations",
"flag-junit",
- "ravenwood-junit",
],
libs: [
@@ -112,17 +59,20 @@ android_ravenwood_test {
name: "PowerStatsTestsRavenwood",
static_libs: [
"services.core",
- "modules-utils-binary-xml",
+ "coretests-aidl",
+ "ravenwood-junit",
+ "truth",
"androidx.annotation_annotation",
"androidx.test.rules",
- "truth",
+ "androidx.test.uiautomator_uiautomator",
+ "modules-utils-binary-xml",
+ "flag-junit",
],
srcs: [
- ":power_stats_ravenwood_tests",
-
- "src/com/android/server/power/stats/BatteryUsageStatsRule.java",
- "src/com/android/server/power/stats/MockBatteryStatsImpl.java",
- "src/com/android/server/power/stats/MockClock.java",
+ "src/com/android/server/power/stats/*.java",
+ ],
+ java_resources: [
+ "res/xml/power_profile*.xml",
],
auto_gen_config: true,
}
diff --git a/services/tests/powerstatstests/TEST_MAPPING b/services/tests/powerstatstests/TEST_MAPPING
index 6d3db1cb6c23..fb243616292d 100644
--- a/services/tests/powerstatstests/TEST_MAPPING
+++ b/services/tests/powerstatstests/TEST_MAPPING
@@ -12,7 +12,11 @@
"ravenwood-presubmit": [
{
"name": "PowerStatsTestsRavenwood",
- "host": true
+ "host": true,
+ "options": [
+ {"include-filter": "com.android.server.power.stats"},
+ {"exclude-annotation": "android.platform.test.annotations.DisabledOnRavenwood"}
+ ]
}
],
"postsubmit": [
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
index ca7de7c3f325..9975190424b5 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.os.BatteryConsumer;
import android.os.PersistableBundle;
+import android.util.SparseArray;
import android.util.Xml;
import androidx.test.filters.SmallTest;
@@ -43,6 +44,9 @@ public class AggregatedPowerStatsTest {
private static final int TEST_POWER_COMPONENT = 1077;
private static final int APP_1 = 27;
private static final int APP_2 = 42;
+ private static final int COMPONENT_STATE_0 = 0;
+ private static final int COMPONENT_STATE_1 = 1;
+ private static final int COMPONENT_STATE_2 = 2;
private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
private PowerStats.Descriptor mPowerComponentDescriptor;
@@ -59,8 +63,10 @@ public class AggregatedPowerStatsTest {
AggregatedPowerStatsConfig.STATE_SCREEN,
AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
- mPowerComponentDescriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "fan", 2, 3,
- PersistableBundle.forPair("speed", "fast"));
+ SparseArray<String> stateLabels = new SparseArray<>();
+ stateLabels.put(COMPONENT_STATE_1, "one");
+ mPowerComponentDescriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "fan", 2,
+ stateLabels, 1, 3, PersistableBundle.forPair("speed", "fast"));
}
@Test
@@ -107,6 +113,9 @@ public class AggregatedPowerStatsTest {
ps.stats[0] = 100;
ps.stats[1] = 987;
+ ps.stateStats.put(COMPONENT_STATE_0, new long[]{1111});
+ ps.stateStats.put(COMPONENT_STATE_1, new long[]{5000});
+
ps.uidStats.put(APP_1, new long[]{389, 0, 739});
ps.uidStats.put(APP_2, new long[]{278, 314, 628});
@@ -120,11 +129,14 @@ public class AggregatedPowerStatsTest {
ps.stats[0] = 444;
ps.stats[1] = 0;
+ ps.stateStats.clear();
+ ps.stateStats.put(COMPONENT_STATE_1, new long[]{1000});
+ ps.stateStats.put(COMPONENT_STATE_2, new long[]{9000});
+
ps.uidStats.put(APP_1, new long[]{0, 0, 400});
ps.uidStats.put(APP_2, new long[]{100, 200, 300});
stats.addPowerStats(ps, 5000);
-
return stats;
}
@@ -147,6 +159,31 @@ public class AggregatedPowerStatsTest {
AggregatedPowerStatsConfig.SCREEN_STATE_OTHER))
.isEqualTo(new long[]{222, 0});
+ assertThat(getStateStats(stats, COMPONENT_STATE_0,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON))
+ .isEqualTo(new long[]{1111});
+
+ assertThat(getStateStats(stats, COMPONENT_STATE_1,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON))
+ .isEqualTo(new long[]{5500});
+
+ assertThat(getStateStats(stats, COMPONENT_STATE_1,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER))
+ .isEqualTo(new long[]{500});
+
+ assertThat(getStateStats(stats, COMPONENT_STATE_2,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON))
+ .isEqualTo(new long[]{4500});
+
+ assertThat(getStateStats(stats, COMPONENT_STATE_2,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER))
+ .isEqualTo(new long[]{4500});
+
assertThat(getUidDeviceStats(stats,
APP_1,
AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
@@ -191,14 +228,26 @@ public class AggregatedPowerStatsTest {
}
private static long[] getDeviceStats(AggregatedPowerStats stats, int... states) {
- long[] out = new long[states.length];
- stats.getPowerComponentStats(TEST_POWER_COMPONENT).getDeviceStats(out, states);
+ PowerComponentAggregatedPowerStats powerComponentStats =
+ stats.getPowerComponentStats(TEST_POWER_COMPONENT);
+ long[] out = new long[powerComponentStats.getPowerStatsDescriptor().statsArrayLength];
+ powerComponentStats.getDeviceStats(out, states);
+ return out;
+ }
+
+ private static long[] getStateStats(AggregatedPowerStats stats, int key, int... states) {
+ PowerComponentAggregatedPowerStats powerComponentStats =
+ stats.getPowerComponentStats(TEST_POWER_COMPONENT);
+ long[] out = new long[powerComponentStats.getPowerStatsDescriptor().stateStatsArrayLength];
+ powerComponentStats.getStateStats(out, key, states);
return out;
}
private static long[] getUidDeviceStats(AggregatedPowerStats stats, int uid, int... states) {
- long[] out = new long[states.length];
- stats.getPowerComponentStats(TEST_POWER_COMPONENT).getUidStats(out, uid, states);
+ PowerComponentAggregatedPowerStats powerComponentStats =
+ stats.getPowerComponentStats(TEST_POWER_COMPONENT);
+ long[] out = new long[powerComponentStats.getPowerStatsDescriptor().uidStatsArrayLength];
+ powerComponentStats.getUidStats(out, uid, states);
return out;
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java
index 3ab1c2eab6ca..9b45ca79fdab 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java
@@ -16,9 +16,11 @@
package com.android.server.power.stats;
-
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
import android.os.BatteryManager;
import android.os.BatteryUsageStats;
import android.platform.test.ravenwood.RavenwoodRule;
@@ -28,6 +30,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.PowerProfile;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,6 +49,11 @@ public class BatteryChargeCalculatorTest {
public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
.setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0);
+ @Before
+ public void setup() {
+ mStatsRule.getBatteryStats().onSystemReady(mock(Context.class));
+ }
+
@Test
public void testDischargeTotals() {
// Nominal battery capacity should be ignored
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index 997b7712a9dd..0a9c8c00444a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -36,6 +36,7 @@ import android.hardware.power.stats.EnergyConsumerType;
import android.hardware.power.stats.EnergyMeasurement;
import android.hardware.power.stats.PowerEntity;
import android.hardware.power.stats.StateResidencyResult;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.power.PowerStatsInternal;
import android.util.IntArray;
import android.util.SparseArray;
@@ -47,6 +48,7 @@ import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.PowerProfile;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import java.util.Arrays;
@@ -59,7 +61,10 @@ import java.util.concurrent.CompletableFuture;
* atest FrameworksServicesTests:BatteryExternalStatsWorkerTest
*/
@SuppressWarnings("GuardedBy")
+@android.platform.test.annotations.DisabledOnRavenwood
public class BatteryExternalStatsWorkerTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
private BatteryExternalStatsWorker mBatteryExternalStatsWorker;
private TestBatteryStatsImpl mBatteryStatsImpl;
private TestPowerStatsInternal mPowerStatsInternal;
@@ -215,7 +220,8 @@ public class BatteryExternalStatsWorkerTest {
public class TestBatteryStatsImpl extends BatteryStatsImpl {
public TestBatteryStatsImpl(Context context) {
- super(Clock.SYSTEM_CLOCK, null, null, null, null, null, null);
+ super(new BatteryStatsConfig.Builder().build(), Clock.SYSTEM_CLOCK, null, null, null,
+ null, null, null);
mPowerProfile = new PowerProfile(context, true /* forTest */);
SparseArray<int[]> cpusByPolicy = new SparseArray<>();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java
index 4d3fcb611f24..ad05b5124955 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java
@@ -18,25 +18,37 @@ package com.android.server.power.stats;
import static android.os.BatteryStats.STATS_SINCE_CHARGED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import android.app.ActivityManager;
import android.os.BatteryStats;
import android.os.WorkSource;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.util.ArrayMap;
import android.view.Display;
import androidx.test.filters.SmallTest;
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
/**
* Test BatteryStatsImpl onBatteryBackgroundTimeBase TimeBase.
*/
-public class BatteryStatsBackgroundStatsTest extends TestCase {
+public class BatteryStatsBackgroundStatsTest {
+
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
private static final int UID = 10500;
/** Test that BatteryStatsImpl.Uid.mOnBatteryBackgroundTimeBase works correctly. */
@SmallTest
+ @Test
public void testBgTimeBase() throws Exception {
final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -105,6 +117,7 @@ public class BatteryStatsBackgroundStatsTest extends TestCase {
/** Test that BatteryStatsImpl.Uid.mOnBatteryScreenOffBackgroundTimeBase works correctly. */
@SmallTest
+ @Test
public void testScreenOffBgTimeBase() throws Exception {
final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -153,6 +166,7 @@ public class BatteryStatsBackgroundStatsTest extends TestCase {
}
@SmallTest
+ @Test
public void testWifiScan() throws Exception {
final MockClock clocks = new MockClock();
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -195,11 +209,13 @@ public class BatteryStatsBackgroundStatsTest extends TestCase {
}
@SmallTest
+ @Test
public void testAppBluetoothScan() throws Exception {
doTestAppBluetoothScanInternal(new WorkSource(UID));
}
@SmallTest
+ @Test
public void testAppBluetoothScan_workChain() throws Exception {
WorkSource ws = new WorkSource();
ws.createWorkChain().addNode(UID, "foo");
@@ -275,6 +291,7 @@ public class BatteryStatsBackgroundStatsTest extends TestCase {
}
@SmallTest
+ @Test
public void testJob() throws Exception {
final MockClock clocks = new MockClock();
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -336,6 +353,7 @@ public class BatteryStatsBackgroundStatsTest extends TestCase {
}
@SmallTest
+ @Test
public void testSyncs() throws Exception {
final MockClock clocks = new MockClock();
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java
index 3f101a96d36c..4dfc3fcec916 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java
@@ -16,20 +16,20 @@
package com.android.server.power.stats;
+import static org.junit.Assert.assertEquals;
+
import android.os.Binder;
import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.util.ArraySet;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderTransactionNameResolver;
-import junit.framework.TestCase;
-
+import org.junit.Rule;
import org.junit.Test;
-import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Collection;
@@ -37,9 +37,14 @@ import java.util.Collection;
/**
* Test cases for android.os.BatteryStats, system server Binder call stats.
*/
-@RunWith(AndroidJUnit4.class)
@SmallTest
-public class BatteryStatsBinderCallStatsTest extends TestCase {
+@android.platform.test.annotations.DisabledOnRavenwood(blockedBy = BinderCallsStats.class)
+public class BatteryStatsBinderCallStatsTest {
+
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
private static final int TRANSACTION_CODE1 = 100;
private static final int TRANSACTION_CODE2 = 101;
@@ -89,7 +94,6 @@ public class BatteryStatsBinderCallStatsTest extends TestCase {
assertEquals(500, value.recordedCpuTimeMicros);
}
-
@Test
public void testProportionalSystemServiceUsage_noStatsForSomeMethods() throws Exception {
final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
index 6e62147ac6c1..eff1b7b852d9 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
@@ -118,7 +118,8 @@ public class BatteryStatsCpuTimesTest {
mClocks = new MockClock();
Handler handler = new Handler(Looper.getMainLooper());
mPowerStatsUidResolver = new PowerStatsUidResolver();
- mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks, null, handler, mPowerStatsUidResolver)
+ mBatteryStatsImpl = new MockBatteryStatsImpl(MockBatteryStatsImpl.DEFAULT_CONFIG,
+ mClocks, null, handler, mPowerStatsUidResolver)
.setTestCpuScalingPolicies()
.setKernelCpuUidUserSysTimeReader(mCpuUidUserSysTimeReader)
.setKernelCpuUidFreqTimeReader(mCpuUidFreqTimeReader)
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index c58c92b47dd3..e40a3e314e58 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -403,7 +403,7 @@ public class BatteryStatsHistoryTest {
@Test
public void recordPowerStats() {
- PowerStats.Descriptor descriptor = new PowerStats.Descriptor(42, "foo", 1, 2,
+ PowerStats.Descriptor descriptor = new PowerStats.Descriptor(42, "foo", 1, null, 0, 2,
new PersistableBundle());
PowerStats powerStats = new PowerStats(descriptor);
powerStats.durationMs = 100;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
index 7ae111711b6b..9a64ce19254b 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
@@ -25,15 +25,21 @@ import android.os.BatteryStatsManager;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
+import org.junit.Rule;
import org.junit.Test;
/**
* Test BatteryStatsManager and CellularBatteryStats to ensure that valid data is being reported
* and that invalid data is not reported.
*/
+@android.platform.test.annotations.DisabledOnRavenwood(reason = "Integration test")
public class BatteryStatsManagerTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
@Test
public void testBatteryUsageStatsDataConsistency() {
BatteryStatsManager bsm = getContext().getSystemService(BatteryStatsManager.class);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index 07cefa9ae878..afbe9159b66a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -170,8 +170,8 @@ public class BatteryStatsNoteTest {
public void testNoteStartWakeLocked_isolatedUid() throws Exception {
final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
PowerStatsUidResolver uidResolver = new PowerStatsUidResolver();
- MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null,
- new Handler(Looper.getMainLooper()), uidResolver);
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(MockBatteryStatsImpl.DEFAULT_CONFIG,
+ clocks, null, new Handler(Looper.getMainLooper()), uidResolver);
int pid = 10;
String name = "name";
@@ -212,8 +212,8 @@ public class BatteryStatsNoteTest {
public void testNoteStartWakeLocked_isolatedUidRace() throws Exception {
final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
PowerStatsUidResolver uidResolver = new PowerStatsUidResolver();
- MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null,
- new Handler(Looper.getMainLooper()), uidResolver);
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(MockBatteryStatsImpl.DEFAULT_CONFIG,
+ clocks, null, new Handler(Looper.getMainLooper()), uidResolver);
int pid = 10;
String name = "name";
@@ -256,8 +256,8 @@ public class BatteryStatsNoteTest {
public void testNoteLongPartialWakelockStart_isolatedUid() throws Exception {
final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
PowerStatsUidResolver uidResolver = new PowerStatsUidResolver();
- MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null,
- new Handler(Looper.getMainLooper()), uidResolver);
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(MockBatteryStatsImpl.DEFAULT_CONFIG,
+ clocks, null, new Handler(Looper.getMainLooper()), uidResolver);
bi.setRecordAllHistoryLocked(true);
bi.forceRecordAllHistory();
@@ -311,8 +311,8 @@ public class BatteryStatsNoteTest {
public void testNoteLongPartialWakelockStart_isolatedUidRace() throws Exception {
final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
PowerStatsUidResolver uidResolver = new PowerStatsUidResolver();
- MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null,
- new Handler(Looper.getMainLooper()), uidResolver);
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(MockBatteryStatsImpl.DEFAULT_CONFIG,
+ clocks, null, new Handler(Looper.getMainLooper()), uidResolver);
bi.setRecordAllHistoryLocked(true);
bi.forceRecordAllHistory();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
index a0fb631812f4..d29bf1abd7a3 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
@@ -18,21 +18,32 @@ package com.android.server.power.stats;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+
import android.content.Context;
import android.os.BatteryManager;
+import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
+import java.nio.file.Files;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class BatteryStatsResetTest {
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
private static final int BATTERY_NOMINAL_VOLTAGE_MV = 3700;
private static final int BATTERY_CAPACITY_UAH = 4_000_000;
private static final int BATTERY_CHARGE_RATE_SECONDS_PER_LEVEL = 100;
@@ -79,13 +90,11 @@ public class BatteryStatsResetTest {
private long mBatteryChargeTimeToFullSeconds;
@Before
- public void setUp() {
- final Context context = InstrumentationRegistry.getContext();
-
+ public void setUp() throws IOException {
mMockClock = new MockClock();
- mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock, context.getFilesDir());
- mBatteryStatsImpl.onSystemReady();
-
+ mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock,
+ Files.createTempDirectory("BatteryStatsResetTest").toFile());
+ mBatteryStatsImpl.onSystemReady(mock(Context.class));
// Set up the battery state. Start off with a fully charged plugged in battery.
mBatteryStatus = BatteryManager.BATTERY_STATUS_FULL;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
index 05d8a005d21e..3931201aaf03 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
@@ -28,6 +28,7 @@ import android.content.pm.UserInfo;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.util.ArraySet;
import androidx.test.InstrumentationRegistry;
@@ -38,6 +39,7 @@ import androidx.test.uiautomator.UiDevice;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,8 +48,10 @@ import java.util.concurrent.TimeUnit;
@LargeTest
@RunWith(AndroidJUnit4.class)
-@android.platform.test.annotations.IgnoreUnderRavenwood
+@android.platform.test.annotations.DisabledOnRavenwood(reason = "Integration test")
public class BatteryStatsUserLifecycleTests {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
private static final long POLL_INTERVAL_MS = 500;
private static final long USER_REMOVE_TIMEOUT_MS = 5_000;
@@ -65,6 +69,10 @@ public class BatteryStatsUserLifecycleTests {
@BeforeClass
public static void setUpOnce() {
+ if (RavenwoodRule.isOnRavenwood()) {
+ return;
+ }
+
assumeTrue(UserManager.getMaxSupportedUsers() > 1);
}
@@ -87,7 +95,7 @@ public class BatteryStatsUserLifecycleTests {
final boolean[] userStopped = new boolean[1];
CountDownLatch stopUserLatch = new CountDownLatch(1);
- mIam.stopUser(mTestUserId, true, new IStopUserCallback.Stub() {
+ mIam.stopUserWithCallback(mTestUserId, new IStopUserCallback.Stub() {
@Override
public void userStopped(int userId) throws RemoteException {
userStopped[0] = true;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 296ad0e939de..2d7cb2245c0a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -24,7 +24,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
-import android.annotation.XmlRes;
+import android.content.Context;
+import android.content.res.Resources;
import android.net.NetworkStats;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
@@ -35,9 +36,9 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.UidBatteryConsumer;
import android.os.UserBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.util.SparseArray;
-
-import androidx.test.InstrumentationRegistry;
+import android.util.Xml;
import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.PowerProfile;
@@ -47,6 +48,7 @@ import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.mockito.stubbing.Answer;
+import org.xmlpull.v1.XmlPullParser;
import java.io.File;
import java.io.IOException;
@@ -81,6 +83,7 @@ public class BatteryUsageStatsRule implements TestRule {
private boolean[] mSupportedStandardBuckets;
private String[] mCustomPowerComponentNames;
private Throwable mThrowable;
+ private final BatteryStatsImpl.BatteryStatsConfig.Builder mBatteryStatsConfigBuilder;
public BatteryUsageStatsRule() {
this(0);
@@ -94,6 +97,11 @@ public class BatteryUsageStatsRule implements TestRule {
mCpusByPolicy.put(4, new int[]{4, 5, 6, 7});
mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
+ mBatteryStatsConfigBuilder = new BatteryStatsImpl.BatteryStatsConfig.Builder()
+ .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU,
+ 10000)
+ .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ 10000);
}
private void initBatteryStats() {
@@ -107,7 +115,8 @@ public class BatteryUsageStatsRule implements TestRule {
}
clearDirectory();
}
- mBatteryStats = new MockBatteryStatsImpl(mMockClock, mHistoryDir, mHandler);
+ mBatteryStats = new MockBatteryStatsImpl(mBatteryStatsConfigBuilder.build(),
+ mMockClock, mHistoryDir, mHandler, new PowerStatsUidResolver());
mBatteryStats.setPowerProfile(mPowerProfile);
mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
synchronized (mBatteryStats) {
@@ -116,8 +125,6 @@ public class BatteryUsageStatsRule implements TestRule {
}
mBatteryStats.informThatAllExternalStatsAreFlushed();
- mBatteryStats.onSystemReady();
-
if (mDisplayCount != -1) {
mBatteryStats.setDisplayCountLocked(mDisplayCount);
}
@@ -148,11 +155,27 @@ public class BatteryUsageStatsRule implements TestRule {
return this;
}
- public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {
- mPowerProfile.forceInitForTesting(InstrumentationRegistry.getContext(), xmlId);
+ public BatteryUsageStatsRule setTestPowerProfile(String resourceName) {
+ mPowerProfile.initForTesting(resolveParser(resourceName));
return this;
}
+ public static XmlPullParser resolveParser(String resourceName) {
+ if (RavenwoodRule.isOnRavenwood()) {
+ try {
+ return Xml.resolvePullParser(BatteryUsageStatsRule.class.getClassLoader()
+ .getResourceAsStream("res/xml/" + resourceName + ".xml"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ Context context = androidx.test.InstrumentationRegistry.getContext();
+ Resources resources = context.getResources();
+ int resId = resources.getIdentifier(resourceName, "xml", context.getPackageName());
+ return resources.getXml(resId);
+ }
+ }
+
public BatteryUsageStatsRule setCpuScalingPolicy(int policy, int[] relatedCpus,
int[] frequencies) {
if (mDefaultCpuScalingPolicy) {
@@ -265,6 +288,12 @@ public class BatteryUsageStatsRule implements TestRule {
return this;
}
+ public BatteryUsageStatsRule setPowerStatsThrottlePeriodMillis(int powerComponent,
+ long throttleMs) {
+ mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis(powerComponent, throttleMs);
+ return this;
+ }
+
public BatteryUsageStatsRule startWithScreenOn(boolean screenOn) {
mScreenOn = screenOn;
return this;
@@ -291,23 +320,21 @@ public class BatteryUsageStatsRule implements TestRule {
}
private void before() {
- initBatteryStats();
HandlerThread bgThread = new HandlerThread("bg thread");
bgThread.setUncaughtExceptionHandler((thread, throwable)-> {
mThrowable = throwable;
});
bgThread.start();
mHandler = new Handler(bgThread.getLooper());
- mBatteryStats.setHandler(mHandler);
+
+ initBatteryStats();
mBatteryStats.setOnBatteryInternal(true);
mBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0);
mBatteryStats.getOnBatteryScreenOffTimeBase().setRunning(!mScreenOn, 0, 0);
}
private void after() throws Throwable {
- if (mHandler != null) {
- waitForBackgroundThread();
- }
+ waitForBackgroundThread();
}
public void waitForBackgroundThread() throws Throwable {
@@ -316,11 +343,12 @@ public class BatteryUsageStatsRule implements TestRule {
}
ConditionVariable done = new ConditionVariable();
- mHandler.post(done::open);
- assertThat(done.block(10000)).isTrue();
-
- if (mThrowable != null) {
- throw mThrowable;
+ if (mHandler.post(done::open)) {
+ boolean success = done.block(5000);
+ if (mThrowable != null) {
+ throw mThrowable;
+ }
+ assertThat(success).isTrue();
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
index 29e2f5ee163a..e4ab227a4840 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
@@ -46,6 +46,7 @@ import android.os.IBinder;
import android.os.PowerManager;
import android.os.Process;
import android.os.SystemClock;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.DebugUtils;
@@ -74,9 +75,11 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
@LargeTest
-@RunWith(AndroidJUnit4.class)
-@android.platform.test.annotations.IgnoreUnderRavenwood
+@android.platform.test.annotations.DisabledOnRavenwood(reason = "Integration test")
public class BstatsCpuTimesValidationTest {
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
private static final String TAG = BstatsCpuTimesValidationTest.class.getSimpleName();
private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp";
@@ -112,10 +115,15 @@ public class BstatsCpuTimesValidationTest {
private static boolean sCpuFreqTimesAvailable;
private static boolean sPerProcStateTimesAvailable;
- @Rule public TestName testName = new TestName();
+ @Rule(order = 1)
+ public TestName testName = new TestName();
@BeforeClass
public static void setupOnce() throws Exception {
+ if (RavenwoodRule.isOnRavenwood()) {
+ return;
+ }
+
sContext = InstrumentationRegistry.getContext();
sUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
sContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
@@ -127,6 +135,10 @@ public class BstatsCpuTimesValidationTest {
@AfterClass
public static void tearDownOnce() throws Exception {
+ if (RavenwoodRule.isOnRavenwood()) {
+ return;
+ }
+
executeCmd("cmd deviceidle whitelist -" + TEST_PKG);
if (sBatteryStatsConstsUpdated) {
Settings.Global.putString(sContext.getContentResolver(),
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
index 64d5414bf66c..ad2939284471 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
@@ -23,65 +23,127 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
-import android.content.Context;
-import android.hardware.power.stats.EnergyConsumer;
-import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyConsumerType;
import android.os.BatteryConsumer;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
-import android.power.PowerStatsInternal;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.util.SparseArray;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.frameworks.powerstatstests.R;
+import com.android.internal.os.Clock;
import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.PowerStats;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParserException;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
+import java.io.IOException;
+import java.util.function.IntSupplier;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class CpuPowerStatsCollectorTest {
+
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
private static final int ISOLATED_UID = 99123;
private static final int UID_1 = 42;
private static final int UID_2 = 99;
- private Context mContext;
private final MockClock mMockClock = new MockClock();
private final HandlerThread mHandlerThread = new HandlerThread("test");
private Handler mHandler;
private PowerStats mCollectedStats;
- private PowerProfile mPowerProfile;
+ private PowerProfile mPowerProfile = new PowerProfile();
@Mock
private PowerStatsUidResolver mUidResolver;
@Mock
private CpuPowerStatsCollector.KernelCpuStatsReader mMockKernelCpuStatsReader;
@Mock
- private PowerStatsInternal mPowerStatsInternal;
+ private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
private CpuScalingPolicies mCpuScalingPolicies;
+ private class TestInjector implements CpuPowerStatsCollector.Injector {
+ private final int mDefaultCpuPowerBrackets;
+ private final int mDefaultCpuPowerBracketsPerEnergyConsumer;
+
+ TestInjector(int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) {
+ mDefaultCpuPowerBrackets = defaultCpuPowerBrackets;
+ mDefaultCpuPowerBracketsPerEnergyConsumer = defaultCpuPowerBracketsPerEnergyConsumer;
+ }
+
+ @Override
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ @Override
+ public Clock getClock() {
+ return mMockClock;
+ }
+
+ @Override
+ public PowerStatsUidResolver getUidResolver() {
+ return mUidResolver;
+ }
+
+ @Override
+ public CpuScalingPolicies getCpuScalingPolicies() {
+ return mCpuScalingPolicies;
+ }
+
+ @Override
+ public PowerProfile getPowerProfile() {
+ return mPowerProfile;
+ }
+
+ @Override
+ public CpuPowerStatsCollector.KernelCpuStatsReader getKernelCpuStatsReader() {
+ return mMockKernelCpuStatsReader;
+ }
+
+ @Override
+ public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+ return mConsumedEnergyRetriever;
+ }
+
+ @Override
+ public IntSupplier getVoltageSupplier() {
+ return () -> 3500;
+ }
+
+ @Override
+ public int getDefaultCpuPowerBrackets() {
+ return mDefaultCpuPowerBrackets;
+ }
+
+ @Override
+ public int getDefaultCpuPowerBracketsPerEnergyConsumer() {
+ return mDefaultCpuPowerBracketsPerEnergyConsumer;
+ }
+ };
+
@Before
- public void setup() {
+ public void setup() throws XmlPullParserException, IOException {
MockitoAnnotations.initMocks(this);
- mContext = InstrumentationRegistry.getContext();
-
mHandlerThread.start();
mHandler = mHandlerThread.getThreadHandler();
- when(mMockKernelCpuStatsReader.nativeIsSupportedFeature()).thenReturn(true);
+ when(mMockKernelCpuStatsReader.isSupportedFeature()).thenReturn(true);
when(mUidResolver.mapUid(anyInt())).thenAnswer(invocation -> {
int uid = invocation.getArgument(0);
if (uid == ISOLATED_UID) {
@@ -90,12 +152,13 @@ public class CpuPowerStatsCollectorTest {
return uid;
}
});
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(anyInt())).thenReturn(new int[0]);
}
@Test
public void powerBrackets_specifiedInPowerProfile() {
- mPowerProfile = new PowerProfile(mContext);
- mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test_power_brackets);
+ mPowerProfile.initForTesting(
+ BatteryUsageStatsRule.resolveParser("power_profile_test_power_brackets"));
mCpuScalingPolicies = new CpuScalingPolicies(
new SparseArray<>() {{
put(0, new int[]{0});
@@ -114,8 +177,7 @@ public class CpuPowerStatsCollectorTest {
@Test
public void powerBrackets_default_noEnergyConsumers() {
- mPowerProfile = new PowerProfile(mContext);
- mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+ mPowerProfile.initForTesting(BatteryUsageStatsRule.resolveParser("power_profile_test"));
mockCpuScalingPolicies(2);
CpuPowerStatsCollector collector = createCollector(3, 0);
@@ -134,8 +196,7 @@ public class CpuPowerStatsCollectorTest {
@Test
public void powerBrackets_moreBracketsThanStates() {
- mPowerProfile = new PowerProfile(mContext);
- mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+ mPowerProfile.initForTesting(BatteryUsageStatsRule.resolveParser("power_profile_test"));
mockCpuScalingPolicies(2);
CpuPowerStatsCollector collector = createCollector(8, 0);
@@ -146,8 +207,7 @@ public class CpuPowerStatsCollectorTest {
@Test
public void powerBrackets_energyConsumers() throws Exception {
- mPowerProfile = new PowerProfile(mContext);
- mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+ mPowerProfile.initForTesting(BatteryUsageStatsRule.resolveParser("power_profile_test"));
mockCpuScalingPolicies(2);
mockEnergyConsumers();
@@ -159,8 +219,7 @@ public class CpuPowerStatsCollectorTest {
@Test
public void powerStatsDescriptor() throws Exception {
- mPowerProfile = new PowerProfile(mContext);
- mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+ mPowerProfile.initForTesting(BatteryUsageStatsRule.resolveParser("power_profile_test"));
mockCpuScalingPolicies(2);
mockEnergyConsumers();
@@ -170,8 +229,8 @@ public class CpuPowerStatsCollectorTest {
assertThat(descriptor.name).isEqualTo("cpu");
assertThat(descriptor.statsArrayLength).isEqualTo(13);
assertThat(descriptor.uidStatsArrayLength).isEqualTo(5);
- CpuPowerStatsCollector.CpuStatsArrayLayout layout =
- new CpuPowerStatsCollector.CpuStatsArrayLayout();
+ CpuPowerStatsLayout layout =
+ new CpuPowerStatsLayout();
layout.fromExtras(descriptor.extras);
long[] deviceStats = new long[descriptor.statsArrayLength];
@@ -209,8 +268,8 @@ public class CpuPowerStatsCollectorTest {
mockEnergyConsumers();
CpuPowerStatsCollector collector = createCollector(8, 0);
- CpuPowerStatsCollector.CpuStatsArrayLayout layout =
- new CpuPowerStatsCollector.CpuStatsArrayLayout();
+ CpuPowerStatsLayout layout =
+ new CpuPowerStatsLayout();
layout.fromExtras(collector.getPowerStatsDescriptor().extras);
mockKernelCpuStats(new long[]{1111, 2222, 3333},
@@ -296,10 +355,9 @@ public class CpuPowerStatsCollectorTest {
private CpuPowerStatsCollector createCollector(int defaultCpuPowerBrackets,
int defaultCpuPowerBracketsPerEnergyConsumer) {
- CpuPowerStatsCollector collector = new CpuPowerStatsCollector(mCpuScalingPolicies,
- mPowerProfile, mHandler, mMockKernelCpuStatsReader, mUidResolver,
- () -> mPowerStatsInternal, () -> 3500, 60_000, mMockClock,
- defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer);
+ CpuPowerStatsCollector collector = new CpuPowerStatsCollector(
+ new TestInjector(defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer),
+ 0);
collector.addConsumer(stats -> mCollectedStats = stats);
collector.setEnabled(true);
return collector;
@@ -307,7 +365,7 @@ public class CpuPowerStatsCollectorTest {
private void mockKernelCpuStats(long[] deviceStats, SparseArray<long[]> uidToCpuStats,
long expectedLastUpdateTimestampMs, long newLastUpdateTimestampMs) {
- when(mMockKernelCpuStatsReader.nativeReadCpuStats(
+ when(mMockKernelCpuStatsReader.readCpuStats(
any(CpuPowerStatsCollector.KernelCpuStatsCallback.class),
any(int[].class), anyLong(), any(long[].class), any(long[].class)))
.thenAnswer(invocation -> {
@@ -335,63 +393,18 @@ public class CpuPowerStatsCollectorTest {
});
}
- @SuppressWarnings("unchecked")
- private void mockEnergyConsumers() throws Exception {
- when(mPowerStatsInternal.getEnergyConsumerInfo())
- .thenReturn(new EnergyConsumer[]{
- new EnergyConsumer() {{
- id = 1;
- type = EnergyConsumerType.CPU_CLUSTER;
- ordinal = 0;
- name = "CPU0";
- }},
- new EnergyConsumer() {{
- id = 2;
- type = EnergyConsumerType.CPU_CLUSTER;
- ordinal = 1;
- name = "CPU4";
- }},
- new EnergyConsumer() {{
- id = 3;
- type = EnergyConsumerType.BLUETOOTH;
- name = "BT";
- }},
- });
-
- CompletableFuture<EnergyConsumerResult[]> future1 = mock(CompletableFuture.class);
- when(future1.get(anyLong(), any(TimeUnit.class)))
- .thenReturn(new EnergyConsumerResult[]{
- new EnergyConsumerResult() {{
- id = 1;
- energyUWs = 1000;
- }},
- new EnergyConsumerResult() {{
- id = 2;
- energyUWs = 2000;
- }}
- });
-
- CompletableFuture<EnergyConsumerResult[]> future2 = mock(CompletableFuture.class);
- when(future2.get(anyLong(), any(TimeUnit.class)))
- .thenReturn(new EnergyConsumerResult[]{
- new EnergyConsumerResult() {{
- id = 1;
- energyUWs = 1500;
- }},
- new EnergyConsumerResult() {{
- id = 2;
- energyUWs = 2700;
- }}
- });
-
- when(mPowerStatsInternal.getEnergyConsumedAsync(eq(new int[]{1, 2})))
- .thenReturn(future1)
- .thenReturn(future2);
+ private void mockEnergyConsumers() {
+ reset(mConsumedEnergyRetriever);
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.CPU_CLUSTER))
+ .thenReturn(new int[]{1, 2});
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{1, 2})))
+ .thenReturn(new long[]{1000, 2000})
+ .thenReturn(new long[]{1500, 2700});
}
private static int[] getScalingStepToPowerBracketMap(CpuPowerStatsCollector collector) {
- CpuPowerStatsCollector.CpuStatsArrayLayout layout =
- new CpuPowerStatsCollector.CpuStatsArrayLayout();
+ CpuPowerStatsLayout layout =
+ new CpuPowerStatsLayout();
layout.fromExtras(collector.getPowerStatsDescriptor().extras);
return layout.getScalingStepToPowerBracketMap();
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
index cbce7e804de5..70c40f5052f0 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
@@ -28,6 +28,8 @@ import android.os.IBinder;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.RavenwoodFlagsValueProvider;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.provider.DeviceConfig;
import androidx.test.InstrumentationRegistry;
@@ -52,11 +54,15 @@ import java.util.regex.Pattern;
@RunWith(AndroidJUnit4.class)
@LargeTest
-@android.platform.test.annotations.IgnoreUnderRavenwood
+@android.platform.test.annotations.DisabledOnRavenwood(reason = "Integration test")
public class CpuPowerStatsCollectorValidationTest {
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ @Rule(order = 1)
+ public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood()
+ ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
+ : DeviceFlagsValueProvider.createCheckFlagsRule();
private static final int WORK_DURATION_MS = 2000;
private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp";
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsProcessorTest.java
index 5c0e26887505..6b5da81954d0 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsProcessorTest.java
@@ -30,6 +30,7 @@ import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SC
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
import android.os.BatteryConsumer;
import android.os.PersistableBundle;
@@ -55,7 +56,7 @@ import java.util.Map;
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class CpuAggregatedPowerStatsProcessorTest {
+public class CpuPowerStatsProcessorTest {
@Rule(order = 0)
public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
.setProvideMainThread(true)
@@ -77,7 +78,7 @@ public class CpuAggregatedPowerStatsProcessorTest {
.setCpuPowerBracket(2, 0, 2);
private AggregatedPowerStatsConfig.PowerComponent mConfig;
- private CpuAggregatedPowerStatsProcessor mProcessor;
+ private CpuPowerStatsProcessor mProcessor;
private MockPowerComponentAggregatedPowerStats mStats;
@Before
@@ -86,7 +87,7 @@ public class CpuAggregatedPowerStatsProcessorTest {
.trackDeviceStates(STATE_POWER, STATE_SCREEN)
.trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE);
- mProcessor = new CpuAggregatedPowerStatsProcessor(
+ mProcessor = new CpuPowerStatsProcessor(
mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies());
}
@@ -197,7 +198,7 @@ public class CpuAggregatedPowerStatsProcessorTest {
private static class MockPowerComponentAggregatedPowerStats extends
PowerComponentAggregatedPowerStats {
- private final CpuPowerStatsCollector.CpuStatsArrayLayout mStatsLayout;
+ private final CpuPowerStatsLayout mStatsLayout;
private final PowerStats.Descriptor mDescriptor;
private HashMap<String, long[]> mDeviceStats = new HashMap<>();
private HashMap<String, long[]> mUidStats = new HashMap<>();
@@ -207,8 +208,8 @@ public class CpuAggregatedPowerStatsProcessorTest {
MockPowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config,
boolean useEnergyConsumers) {
- super(config);
- mStatsLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout();
+ super(new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
+ mStatsLayout = new CpuPowerStatsLayout();
mStatsLayout.addDeviceSectionCpuTimeByScalingStep(3);
mStatsLayout.addDeviceSectionCpuTimeByCluster(2);
mStatsLayout.addDeviceSectionUsageDuration();
@@ -222,8 +223,8 @@ public class CpuAggregatedPowerStatsProcessorTest {
PersistableBundle extras = new PersistableBundle();
mStatsLayout.toExtras(extras);
mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU,
- mStatsLayout.getDeviceStatsArrayLength(), mStatsLayout.getUidStatsArrayLength(),
- extras);
+ mStatsLayout.getDeviceStatsArrayLength(), null, 0,
+ mStatsLayout.getUidStatsArrayLength(), extras);
}
@Override
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
index e02386656cb5..f035465dd1df 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
@@ -16,16 +16,26 @@
package com.android.server.power.stats;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.ravenwood.RavenwoodRule;
import android.system.suspend.internal.WakeLockInfo;
import androidx.test.filters.SmallTest;
-import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
import java.nio.charset.Charset;
-@android.platform.test.annotations.IgnoreUnderRavenwood
-public class KernelWakelockReaderTest extends TestCase {
+@android.platform.test.annotations.DisabledOnRavenwood(reason = "Kernel dependency")
+public class KernelWakelockReaderTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
/**
* Helper class that builds the mock Kernel module file /d/wakeup_sources.
*/
@@ -105,14 +115,14 @@ public class KernelWakelockReaderTest extends TestCase {
private KernelWakelockReader mReader;
- @Override
+ @Before
public void setUp() throws Exception {
- super.setUp();
mReader = new KernelWakelockReader();
}
// ------------------------- Legacy Wakelock Stats Test ------------------------
@SmallTest
+ @Test
public void testParseEmptyFile() throws Exception {
KernelWakelockStats staleStats = mReader.parseProcWakelocks(new byte[0], 0, true,
new KernelWakelockStats());
@@ -121,6 +131,7 @@ public class KernelWakelockReaderTest extends TestCase {
}
@SmallTest
+ @Test
public void testOnlyHeader() throws Exception {
byte[] buffer = new ProcFileBuilder().getBytes();
@@ -131,6 +142,7 @@ public class KernelWakelockReaderTest extends TestCase {
}
@SmallTest
+ @Test
public void testOneWakelock() throws Exception {
byte[] buffer = new ProcFileBuilder()
.addLine("Wakelock", 34, 123, 456) // Milliseconds
@@ -150,6 +162,7 @@ public class KernelWakelockReaderTest extends TestCase {
}
@SmallTest
+ @Test
public void testTwoWakelocks() throws Exception {
byte[] buffer = new ProcFileBuilder()
.addLine("Wakelock", 1, 10)
@@ -166,6 +179,7 @@ public class KernelWakelockReaderTest extends TestCase {
}
@SmallTest
+ @Test
public void testDuplicateWakelocksAccumulate() throws Exception {
byte[] buffer = new ProcFileBuilder()
.addLine("Wakelock", 1, 10) // Milliseconds
@@ -184,6 +198,7 @@ public class KernelWakelockReaderTest extends TestCase {
}
@SmallTest
+ @Test
public void testWakelocksBecomeStale() throws Exception {
KernelWakelockStats staleStats = new KernelWakelockStats();
@@ -209,6 +224,7 @@ public class KernelWakelockReaderTest extends TestCase {
// -------------------- SystemSuspend Wakelock Stats Test -------------------
@SmallTest
+ @Test
public void testEmptyWakeLockInfoList() {
KernelWakelockStats staleStats = mReader.updateWakelockStats(new WakeLockInfo[0],
new KernelWakelockStats());
@@ -217,6 +233,7 @@ public class KernelWakelockReaderTest extends TestCase {
}
@SmallTest
+ @Test
public void testOneWakeLockInfo() {
WakeLockInfo[] wlStats = new WakeLockInfo[1];
wlStats[0] = createWakeLockInfo("WakeLock", 20, 1000, 500); // Milliseconds
@@ -235,6 +252,7 @@ public class KernelWakelockReaderTest extends TestCase {
}
@SmallTest
+ @Test
public void testTwoWakeLockInfos() {
WakeLockInfo[] wlStats = new WakeLockInfo[2];
wlStats[0] = createWakeLockInfo("WakeLock1", 10, 1000); // Milliseconds
@@ -258,6 +276,7 @@ public class KernelWakelockReaderTest extends TestCase {
}
@SmallTest
+ @Test
public void testWakeLockInfosBecomeStale() {
WakeLockInfo[] wlStats = new WakeLockInfo[1];
wlStats[0] = createWakeLockInfo("WakeLock1", 10, 1000); // Milliseconds
@@ -288,6 +307,7 @@ public class KernelWakelockReaderTest extends TestCase {
// -------------------- Aggregate Wakelock Stats Tests --------------------
@SmallTest
+ @Test
public void testAggregateStatsEmpty() throws Exception {
KernelWakelockStats staleStats = new KernelWakelockStats();
@@ -300,6 +320,7 @@ public class KernelWakelockReaderTest extends TestCase {
}
@SmallTest
+ @Test
public void testAggregateStatsNoNativeWakelocks() throws Exception {
KernelWakelockStats staleStats = new KernelWakelockStats();
@@ -320,6 +341,7 @@ public class KernelWakelockReaderTest extends TestCase {
}
@SmallTest
+ @Test
public void testAggregateStatsNoKernelWakelocks() throws Exception {
KernelWakelockStats staleStats = new KernelWakelockStats();
@@ -339,6 +361,7 @@ public class KernelWakelockReaderTest extends TestCase {
}
@SmallTest
+ @Test
public void testAggregateStatsBothKernelAndNativeWakelocks() throws Exception {
KernelWakelockStats staleStats = new KernelWakelockStats();
@@ -364,6 +387,7 @@ public class KernelWakelockReaderTest extends TestCase {
}
@SmallTest
+ @Test
public void testAggregateStatsUpdate() throws Exception {
KernelWakelockStats staleStats = new KernelWakelockStats();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
index 888a1688c2a1..9b810bc01b4d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
@@ -26,6 +26,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.app.usage.NetworkStatsManager;
import android.net.NetworkCapabilities;
import android.net.NetworkStats;
@@ -34,6 +35,7 @@ import android.os.BatteryStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Process;
import android.os.UidBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.telephony.AccessNetworkConstants;
import android.telephony.ActivityStatsTechSpecificInfo;
import android.telephony.CellSignalStrength;
@@ -46,8 +48,6 @@ import android.telephony.TelephonyManager;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.frameworks.powerstatstests.R;
-
import com.google.common.collect.Range;
import org.junit.Rule;
@@ -56,23 +56,29 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import java.util.ArrayList;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
@SuppressWarnings("GuardedBy")
public class MobileRadioPowerCalculatorTest {
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
private static final double PRECISION = 0.00001;
private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
@Mock
NetworkStatsManager mNetworkStatsManager;
- @Rule
+ @Rule(order = 1)
public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
@Test
public void testCounterBasedModel() {
- mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator)
+ mStatsRule.setTestPowerProfile("power_profile_test_modem_calculator")
.initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
@@ -126,10 +132,10 @@ public class MobileRadioPowerCalculatorTest {
stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
// Note application network activity
- NetworkStats networkStats = new NetworkStats(10000, 1)
- .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
- .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+ NetworkStats networkStats = mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100),
+ mockNetworkStatsEntry("cellular", APP_UID2, 0, 0,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
mStatsRule.setNetworkStats(networkStats);
@@ -192,7 +198,7 @@ public class MobileRadioPowerCalculatorTest {
@Test
public void testCounterBasedModel_multipleDefinedRat() {
- mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator_multiactive)
+ mStatsRule.setTestPowerProfile("power_profile_test_modem_calculator_multiactive")
.initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
@@ -246,10 +252,10 @@ public class MobileRadioPowerCalculatorTest {
stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
// Note application network activity
- NetworkStats networkStats = new NetworkStats(10000, 1)
- .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
- .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+ NetworkStats networkStats = mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100),
+ mockNetworkStatsEntry("cellular", APP_UID2, 0, 0,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
mStatsRule.setNetworkStats(networkStats);
@@ -349,7 +355,7 @@ public class MobileRadioPowerCalculatorTest {
@Test
public void testCounterBasedModel_legacyPowerProfile() {
- mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem")
.initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
@@ -403,10 +409,10 @@ public class MobileRadioPowerCalculatorTest {
stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
// Note application network activity
- NetworkStats networkStats = new NetworkStats(10000, 1)
- .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
- .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+ NetworkStats networkStats = mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100),
+ mockNetworkStatsEntry("cellular", APP_UID2, 0, 0,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
mStatsRule.setNetworkStats(networkStats);
@@ -469,7 +475,7 @@ public class MobileRadioPowerCalculatorTest {
@Test
public void testTimerBasedModel_byProcessState() {
- mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem")
.initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
@@ -521,8 +527,8 @@ public class MobileRadioPowerCalculatorTest {
stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
// Note application network activity
- mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
- .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+ mStatsRule.setNetworkStats(mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 10000, 10000,
@@ -531,8 +537,8 @@ public class MobileRadioPowerCalculatorTest {
uid.setProcessStateForTest(
BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 11000);
- mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
- .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+ mStatsRule.setNetworkStats(mockNetworkStats(12000, 1,
+ mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 12000, 12000,
@@ -586,7 +592,7 @@ public class MobileRadioPowerCalculatorTest {
@Test
public void testMeasuredEnergyBasedModel_mobileRadioActiveTimeModel() {
- mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem")
.setPerUidModemModel(
BatteryStatsImpl.PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME)
.initMeasuredEnergyStatsLocked();
@@ -619,8 +625,8 @@ public class MobileRadioPowerCalculatorTest {
stats.notePhoneOnLocked(9800, 9800);
// Note application network activity
- NetworkStats networkStats = new NetworkStats(10000, 1)
- .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+ NetworkStats networkStats = mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100));
mStatsRule.setNetworkStats(networkStats);
@@ -662,11 +668,9 @@ public class MobileRadioPowerCalculatorTest {
.isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
-
-
@Test
public void testMeasuredEnergyBasedModel_modemActivityInfoRxTxModel() {
- mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator_multiactive)
+ mStatsRule.setTestPowerProfile("power_profile_test_modem_calculator_multiactive")
.setPerUidModemModel(
BatteryStatsImpl.PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX)
.initMeasuredEnergyStatsLocked();
@@ -728,10 +732,10 @@ public class MobileRadioPowerCalculatorTest {
stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
// Note application network activity
- NetworkStats networkStats = new NetworkStats(10000, 1)
- .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 300, 10, 100))
- .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+ NetworkStats networkStats = mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 300, 10, 100),
+ mockNetworkStatsEntry("cellular", APP_UID2, 0, 0,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 2000, 30, 111));
mStatsRule.setNetworkStats(networkStats);
@@ -850,7 +854,7 @@ public class MobileRadioPowerCalculatorTest {
@Test
public void testMeasuredEnergyBasedModel_modemActivityInfoRxTxModel_legacyPowerProfile() {
- mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem")
.setPerUidModemModel(
BatteryStatsImpl.PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX)
.initMeasuredEnergyStatsLocked();
@@ -908,8 +912,8 @@ public class MobileRadioPowerCalculatorTest {
stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
// Note application network activity
- NetworkStats networkStats = new NetworkStats(10000, 1)
- .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+ NetworkStats networkStats = mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100));
mStatsRule.setNetworkStats(networkStats);
@@ -957,7 +961,7 @@ public class MobileRadioPowerCalculatorTest {
@Test
public void testMeasuredEnergyBasedModel_byProcessState() {
- mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem")
.initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
@@ -988,8 +992,8 @@ public class MobileRadioPowerCalculatorTest {
new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
// Note application network activity
- mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
- .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+ mStatsRule.setNetworkStats(mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
stats.noteModemControllerActivity(null, 10_000_000, 10000, 10000, mNetworkStatsManager);
@@ -997,8 +1001,8 @@ public class MobileRadioPowerCalculatorTest {
uid.setProcessStateForTest(
BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 11000);
- mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
- .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+ mStatsRule.setNetworkStats(mockNetworkStats(12000, 1,
+ mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
stats.noteModemControllerActivity(null, 15_000_000, 12000, 12000, mNetworkStatsManager);
@@ -1047,4 +1051,40 @@ public class MobileRadioPowerCalculatorTest {
final ModemActivityInfo emptyMai = new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L);
stats.noteModemControllerActivity(emptyMai, 0, 0, 0, mNetworkStatsManager);
}
+
+ private NetworkStats mockNetworkStats(int elapsedTime, int initialSize,
+ NetworkStats.Entry... entries) {
+ NetworkStats stats;
+ if (RavenwoodRule.isOnRavenwood()) {
+ stats = mock(NetworkStats.class);
+ when(stats.iterator()).thenAnswer(inv -> List.of(entries).iterator());
+ } else {
+ stats = new NetworkStats(elapsedTime, initialSize);
+ for (NetworkStats.Entry entry : entries) {
+ stats = stats.addEntry(entry);
+ }
+ }
+ return stats;
+ }
+
+ private static NetworkStats.Entry mockNetworkStatsEntry(@Nullable String iface, int uid,
+ int set, int tag, int metered, int roaming, int defaultNetwork, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, long operations) {
+ if (RavenwoodRule.isOnRavenwood()) {
+ NetworkStats.Entry entry = mock(NetworkStats.Entry.class);
+ when(entry.getUid()).thenReturn(uid);
+ when(entry.getMetered()).thenReturn(metered);
+ when(entry.getRoaming()).thenReturn(roaming);
+ when(entry.getDefaultNetwork()).thenReturn(defaultNetwork);
+ when(entry.getRxBytes()).thenReturn(rxBytes);
+ when(entry.getRxPackets()).thenReturn(rxPackets);
+ when(entry.getTxBytes()).thenReturn(txBytes);
+ when(entry.getTxPackets()).thenReturn(txPackets);
+ when(entry.getOperations()).thenReturn(operations);
+ return entry;
+ } else {
+ return new NetworkStats.Entry(iface, uid, set, tag, metered,
+ roaming, defaultNetwork, rxBytes, rxPackets, txBytes, txPackets, operations);
+ }
+ }
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
new file mode 100644
index 000000000000..f93c4da3d8d0
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
+
+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.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.net.NetworkStats;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.OutcomeReceiver;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.ActivityStatsTechSpecificInfo;
+import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.ModemActivityInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.IntSupplier;
+import java.util.function.LongSupplier;
+import java.util.function.Supplier;
+
+public class MobileRadioPowerStatsCollectorTest {
+ private static final int APP_UID1 = 42;
+ private static final int APP_UID2 = 24;
+ private static final int APP_UID3 = 44;
+ private static final int ISOLATED_UID = 99123;
+
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
+ @Rule(order = 1)
+ public final BatteryUsageStatsRule mStatsRule =
+ new BatteryUsageStatsRule().setPowerStatsThrottlePeriodMillis(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, 10000);
+
+ private MockBatteryStatsImpl mBatteryStats;
+
+ private final MockClock mClock = mStatsRule.getMockClock();
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private TelephonyManager mTelephony;
+ @Mock
+ private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ @Mock
+ private Supplier<NetworkStats> mNetworkStatsSupplier;
+ @Mock
+ private PowerStatsUidResolver mPowerStatsUidResolver;
+ @Mock
+ private LongSupplier mCallDurationSupplier;
+ @Mock
+ private LongSupplier mScanDurationSupplier;
+
+ private final List<PowerStats> mRecordedPowerStats = new ArrayList<>();
+
+ private MobileRadioPowerStatsCollector.Injector mInjector =
+ new MobileRadioPowerStatsCollector.Injector() {
+ @Override
+ public Handler getHandler() {
+ return mStatsRule.getHandler();
+ }
+
+ @Override
+ public Clock getClock() {
+ return mStatsRule.getMockClock();
+ }
+
+ @Override
+ public PowerStatsUidResolver getUidResolver() {
+ return mPowerStatsUidResolver;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ @Override
+ public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+ return mConsumedEnergyRetriever;
+ }
+
+ @Override
+ public IntSupplier getVoltageSupplier() {
+ return () -> 3500;
+ }
+
+ @Override
+ public Supplier<NetworkStats> getMobileNetworkStatsSupplier() {
+ return mNetworkStatsSupplier;
+ }
+
+ @Override
+ public TelephonyManager getTelephonyManager() {
+ return mTelephony;
+ }
+
+ @Override
+ public LongSupplier getCallDurationSupplier() {
+ return mCallDurationSupplier;
+ }
+
+ @Override
+ public LongSupplier getPhoneSignalScanDurationSupplier() {
+ return mScanDurationSupplier;
+ }
+ };
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
+ when(mPowerStatsUidResolver.mapUid(anyInt())).thenAnswer(invocation -> {
+ int uid = invocation.getArgument(0);
+ if (uid == ISOLATED_UID) {
+ return APP_UID2;
+ } else {
+ return uid;
+ }
+ });
+ mBatteryStats = mStatsRule.getBatteryStats();
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void triggering() throws Throwable {
+ PowerStatsCollector collector = mBatteryStats.getPowerStatsCollector(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO);
+ collector.addConsumer(mRecordedPowerStats::add);
+
+ mBatteryStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ true);
+
+ mockModemActivityInfo(1000, 2000, 3000, 600, new int[]{100, 200, 300, 400, 500});
+
+ // This should trigger a sample collection
+ mBatteryStats.onSystemReady(mContext);
+
+ mStatsRule.waitForBackgroundThread();
+ assertThat(mRecordedPowerStats).hasSize(1);
+
+ mRecordedPowerStats.clear();
+ mStatsRule.setTime(20000, 20000);
+ mBatteryStats.notePhoneOnLocked(mClock.realtime, mClock.uptime);
+ mStatsRule.waitForBackgroundThread();
+ assertThat(mRecordedPowerStats).hasSize(1);
+
+ mRecordedPowerStats.clear();
+ mStatsRule.setTime(40000, 40000);
+ mBatteryStats.notePhoneOffLocked(mClock.realtime, mClock.uptime);
+ mStatsRule.waitForBackgroundThread();
+ assertThat(mRecordedPowerStats).hasSize(1);
+
+ mRecordedPowerStats.clear();
+ mStatsRule.setTime(45000, 55000);
+ mBatteryStats.noteMobileRadioPowerStateLocked(
+ DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, 0, APP_UID1, mClock.realtime,
+ mClock.uptime);
+ mStatsRule.setTime(50001, 50001);
+ // Elapsed time under the throttling threshold - shouldn't trigger stats collection
+ mBatteryStats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ 0, APP_UID1, mClock.realtime, mClock.uptime);
+ mStatsRule.waitForBackgroundThread();
+ assertThat(mRecordedPowerStats).hasSize(1);
+
+ mRecordedPowerStats.clear();
+ mStatsRule.setTime(50002, 50002);
+ mBatteryStats.noteMobileRadioPowerStateLocked(
+ DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, 0, APP_UID1, mClock.realtime,
+ mClock.uptime);
+ mStatsRule.setTime(55000, 50000);
+ // Elapsed time under the throttling threshold - shouldn't trigger stats collection
+ mBatteryStats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ 0, APP_UID1, mClock.realtime, mClock.uptime);
+ mStatsRule.waitForBackgroundThread();
+ assertThat(mRecordedPowerStats).isEmpty();
+ }
+
+ @Test
+ public void collectStats() throws Throwable {
+ PowerStats powerStats = collectPowerStats(true);
+ assertThat(powerStats.durationMs).isEqualTo(100);
+
+ PowerStats.Descriptor descriptor = powerStats.descriptor;
+ MobileRadioPowerStatsLayout layout =
+ new MobileRadioPowerStatsLayout(descriptor);
+ assertThat(layout.getDeviceSleepTime(powerStats.stats)).isEqualTo(200);
+ assertThat(layout.getDeviceIdleTime(powerStats.stats)).isEqualTo(300);
+ assertThat(layout.getDeviceCallTime(powerStats.stats)).isEqualTo(40000);
+ assertThat(layout.getDeviceScanTime(powerStats.stats)).isEqualTo(60000);
+ assertThat(layout.getConsumedEnergy(powerStats.stats, 0))
+ .isEqualTo((64321 - 10000) * 1000 / 3500);
+
+ assertThat(powerStats.stateStats.size()).isEqualTo(2);
+ long[] state1 = powerStats.stateStats.get(MobileRadioPowerStatsCollector.makeStateKey(
+ BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR,
+ ServiceState.FREQUENCY_RANGE_MMWAVE
+ ));
+ assertThat(layout.getStateRxTime(state1)).isEqualTo(6000);
+ assertThat(layout.getStateTxTime(state1, 0)).isEqualTo(1000);
+ assertThat(layout.getStateTxTime(state1, 1)).isEqualTo(2000);
+ assertThat(layout.getStateTxTime(state1, 2)).isEqualTo(3000);
+ assertThat(layout.getStateTxTime(state1, 3)).isEqualTo(4000);
+ assertThat(layout.getStateTxTime(state1, 4)).isEqualTo(5000);
+
+ long[] state2 = powerStats.stateStats.get(MobileRadioPowerStatsCollector.makeStateKey(
+ BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ ServiceState.FREQUENCY_RANGE_LOW
+ ));
+ assertThat(layout.getStateRxTime(state2)).isEqualTo(7000);
+ assertThat(layout.getStateTxTime(state2, 0)).isEqualTo(8000);
+ assertThat(layout.getStateTxTime(state2, 1)).isEqualTo(9000);
+ assertThat(layout.getStateTxTime(state2, 2)).isEqualTo(1000);
+ assertThat(layout.getStateTxTime(state2, 3)).isEqualTo(2000);
+ assertThat(layout.getStateTxTime(state2, 4)).isEqualTo(3000);
+
+ assertThat(powerStats.uidStats.size()).isEqualTo(2);
+ long[] actual1 = powerStats.uidStats.get(APP_UID1);
+ assertThat(layout.getUidRxBytes(actual1)).isEqualTo(1000);
+ assertThat(layout.getUidTxBytes(actual1)).isEqualTo(2000);
+ assertThat(layout.getUidRxPackets(actual1)).isEqualTo(100);
+ assertThat(layout.getUidTxPackets(actual1)).isEqualTo(200);
+
+ // Combines APP_UID2 and ISOLATED_UID
+ long[] actual2 = powerStats.uidStats.get(APP_UID2);
+ assertThat(layout.getUidRxBytes(actual2)).isEqualTo(6000);
+ assertThat(layout.getUidTxBytes(actual2)).isEqualTo(3000);
+ assertThat(layout.getUidRxPackets(actual2)).isEqualTo(60);
+ assertThat(layout.getUidTxPackets(actual2)).isEqualTo(30);
+
+ assertThat(powerStats.uidStats.get(ISOLATED_UID)).isNull();
+ assertThat(powerStats.uidStats.get(APP_UID3)).isNull();
+ }
+
+ @Test
+ public void collectStats_noPerNetworkTypeData() throws Throwable {
+ PowerStats powerStats = collectPowerStats(false);
+ assertThat(powerStats.durationMs).isEqualTo(100);
+
+ PowerStats.Descriptor descriptor = powerStats.descriptor;
+ MobileRadioPowerStatsLayout layout =
+ new MobileRadioPowerStatsLayout(descriptor);
+ assertThat(layout.getDeviceSleepTime(powerStats.stats)).isEqualTo(200);
+ assertThat(layout.getDeviceIdleTime(powerStats.stats)).isEqualTo(300);
+ assertThat(layout.getConsumedEnergy(powerStats.stats, 0))
+ .isEqualTo((64321 - 10000) * 1000 / 3500);
+
+ assertThat(powerStats.stateStats.size()).isEqualTo(1);
+ long[] stateStats = powerStats.stateStats.get(MobileRadioPowerStatsCollector.makeStateKey(
+ AccessNetworkConstants.AccessNetworkType.UNKNOWN,
+ ServiceState.FREQUENCY_RANGE_UNKNOWN
+ ));
+ assertThat(layout.getStateRxTime(stateStats)).isEqualTo(6000);
+ assertThat(layout.getStateTxTime(stateStats, 0)).isEqualTo(1000);
+ assertThat(layout.getStateTxTime(stateStats, 1)).isEqualTo(2000);
+ assertThat(layout.getStateTxTime(stateStats, 2)).isEqualTo(3000);
+ assertThat(layout.getStateTxTime(stateStats, 3)).isEqualTo(4000);
+ assertThat(layout.getStateTxTime(stateStats, 4)).isEqualTo(5000);
+
+ assertThat(powerStats.uidStats.size()).isEqualTo(2);
+ long[] actual1 = powerStats.uidStats.get(APP_UID1);
+ assertThat(layout.getUidRxBytes(actual1)).isEqualTo(1000);
+ assertThat(layout.getUidTxBytes(actual1)).isEqualTo(2000);
+ assertThat(layout.getUidRxPackets(actual1)).isEqualTo(100);
+ assertThat(layout.getUidTxPackets(actual1)).isEqualTo(200);
+
+ // Combines APP_UID2 and ISOLATED_UID
+ long[] actual2 = powerStats.uidStats.get(APP_UID2);
+ assertThat(layout.getUidRxBytes(actual2)).isEqualTo(6000);
+ assertThat(layout.getUidTxBytes(actual2)).isEqualTo(3000);
+ assertThat(layout.getUidRxPackets(actual2)).isEqualTo(60);
+ assertThat(layout.getUidTxPackets(actual2)).isEqualTo(30);
+
+ assertThat(powerStats.uidStats.get(ISOLATED_UID)).isNull();
+ assertThat(powerStats.uidStats.get(APP_UID3)).isNull();
+ }
+
+ @Test
+ public void dump() throws Throwable {
+ PowerStats powerStats = collectPowerStats(true);
+ StringWriter sw = new StringWriter();
+ IndentingPrintWriter pw = new IndentingPrintWriter(sw);
+ powerStats.dump(pw);
+ pw.flush();
+ String dump = sw.toString();
+ assertThat(dump).contains("duration=100");
+ assertThat(dump).contains(
+ "stats=[200, 300, 60000, 40000, " + ((64321 - 10000) * 1000 / 3500) + ", 0, 0, 0]");
+ assertThat(dump).contains("state LTE: [7000, 8000, 9000, 1000, 2000, 3000]");
+ assertThat(dump).contains("state NR MMWAVE: [6000, 1000, 2000, 3000, 4000, 5000]");
+ assertThat(dump).contains("UID 24: [6000, 3000, 60, 30, 0]");
+ assertThat(dump).contains("UID 42: [1000, 2000, 100, 200, 0]");
+ }
+
+ private PowerStats collectPowerStats(boolean perNetworkTypeData) throws Throwable {
+ MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+ collector.setEnabled(true);
+
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(
+ EnergyConsumerType.MOBILE_RADIO)).thenReturn(new int[]{777});
+
+ if (perNetworkTypeData) {
+ mockModemActivityInfo(1000, 2000, 3000,
+ AccessNetworkConstants.AccessNetworkType.NGRAN,
+ ServiceState.FREQUENCY_RANGE_MMWAVE,
+ 600, new int[]{100, 200, 300, 400, 500},
+ AccessNetworkConstants.AccessNetworkType.EUTRAN,
+ ServiceState.FREQUENCY_RANGE_LOW,
+ 700, new int[]{800, 900, 100, 200, 300});
+ } else {
+ mockModemActivityInfo(1000, 2000, 3000, 600, new int[]{100, 200, 300, 400, 500});
+ }
+ mockNetworkStats(1000,
+ 4321, 321, 1234, 23,
+ 4000, 40, 2000, 20);
+
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{777})))
+ .thenReturn(new long[]{10000});
+
+ when(mCallDurationSupplier.getAsLong()).thenReturn(10000L);
+ when(mScanDurationSupplier.getAsLong()).thenReturn(20000L);
+
+ collector.collectStats();
+
+ if (perNetworkTypeData) {
+ mockModemActivityInfo(1100, 2200, 3300,
+ AccessNetworkConstants.AccessNetworkType.NGRAN,
+ ServiceState.FREQUENCY_RANGE_MMWAVE,
+ 6600, new int[]{1100, 2200, 3300, 4400, 5500},
+ AccessNetworkConstants.AccessNetworkType.EUTRAN,
+ ServiceState.FREQUENCY_RANGE_LOW,
+ 7700, new int[]{8800, 9900, 1100, 2200, 3300});
+ } else {
+ mockModemActivityInfo(1100, 2200, 3300, 6600, new int[]{1100, 2200, 3300, 4400, 5500});
+ }
+ mockNetworkStats(1100,
+ 5321, 421, 3234, 223,
+ 8000, 80, 4000, 40);
+
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{777})))
+ .thenReturn(new long[]{64321});
+ when(mCallDurationSupplier.getAsLong()).thenReturn(50000L);
+ when(mScanDurationSupplier.getAsLong()).thenReturn(80000L);
+
+ mStatsRule.setTime(20000, 20000);
+ return collector.collectStats();
+ }
+
+ private void mockModemActivityInfo(long timestamp, int sleepTimeMs, int idleTimeMs,
+ int networkType1, int freqRange1, int rxTimeMs1, @NonNull int[] txTimeMs1,
+ int networkType2, int freqRange2, int rxTimeMs2, @NonNull int[] txTimeMs2) {
+ ModemActivityInfo info = new ModemActivityInfo(timestamp, sleepTimeMs, idleTimeMs,
+ new ActivityStatsTechSpecificInfo[]{
+ new ActivityStatsTechSpecificInfo(networkType1, freqRange1, txTimeMs1,
+ rxTimeMs1),
+ new ActivityStatsTechSpecificInfo(networkType2, freqRange2, txTimeMs2,
+ rxTimeMs2)});
+ doAnswer(invocation -> {
+ OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException>
+ receiver = invocation.getArgument(1);
+ receiver.onResult(info);
+ return null;
+ }).when(mTelephony).requestModemActivityInfo(any(), any());
+ }
+
+ private void mockModemActivityInfo(long timestamp, int sleepTimeMs, int idleTimeMs,
+ int rxTimeMs, @NonNull int[] txTimeMs) {
+ ModemActivityInfo info = new ModemActivityInfo(timestamp, sleepTimeMs, idleTimeMs, txTimeMs,
+ rxTimeMs);
+ doAnswer(invocation -> {
+ OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException>
+ receiver = invocation.getArgument(1);
+ receiver.onResult(info);
+ return null;
+ }).when(mTelephony).requestModemActivityInfo(any(), any());
+ }
+
+ private void mockNetworkStats(long elapsedRealtime,
+ long rxBytes1, long rxPackets1, long txBytes1, long txPackets1,
+ long rxBytes2, long rxPackets2, long txBytes2, long txPackets2) {
+ NetworkStats stats;
+ if (RavenwoodRule.isOnRavenwood()) {
+ stats = mock(NetworkStats.class);
+ List<NetworkStats.Entry> entries = List.of(
+ mockNetworkStatsEntry(APP_UID1, rxBytes1, rxPackets1, txBytes1, txPackets1),
+ mockNetworkStatsEntry(APP_UID2, rxBytes2, rxPackets2, txBytes2, txPackets2),
+ mockNetworkStatsEntry(ISOLATED_UID, rxBytes2 / 2, rxPackets2 / 2, txBytes2 / 2,
+ txPackets2 / 2),
+ mockNetworkStatsEntry(APP_UID3, 314, 281, 314, 281));
+ when(stats.iterator()).thenAnswer(inv -> entries.iterator());
+ } else {
+ stats = new NetworkStats(elapsedRealtime, 1)
+ .addEntry(new NetworkStats.Entry("mobile", APP_UID1, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes1, rxPackets1,
+ txBytes1, txPackets1, 100))
+ .addEntry(new NetworkStats.Entry("mobile", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes2, rxPackets2,
+ txBytes2, txPackets2, 111))
+ .addEntry(new NetworkStats.Entry("mobile", ISOLATED_UID, 0, 0, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes2 / 2, rxPackets2 / 2,
+ txBytes2 / 2, txPackets2 / 2, 111))
+ .addEntry(new NetworkStats.Entry("mobile", APP_UID3, 0, 0, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 314, 281, 314, 281, 111));
+ }
+ when(mNetworkStatsSupplier.get()).thenReturn(stats);
+ }
+
+ private static NetworkStats.Entry mockNetworkStatsEntry(int uid, long rxBytes, long rxPackets,
+ long txBytes, long txPackets) {
+ NetworkStats.Entry entry = mock(NetworkStats.Entry.class);
+ when(entry.getUid()).thenReturn(uid);
+ when(entry.getMetered()).thenReturn(METERED_NO);
+ when(entry.getRoaming()).thenReturn(ROAMING_NO);
+ when(entry.getDefaultNetwork()).thenReturn(DEFAULT_NETWORK_NO);
+ when(entry.getRxBytes()).thenReturn(rxBytes);
+ when(entry.getRxPackets()).thenReturn(rxPackets);
+ when(entry.getTxBytes()).thenReturn(txBytes);
+ when(entry.getTxPackets()).thenReturn(txPackets);
+ when(entry.getOperations()).thenReturn(100L);
+ return entry;
+ }
+
+ @Test
+ public void networkTypeConstants() throws Throwable {
+ Class<AccessNetworkConstants.AccessNetworkType> clazz =
+ AccessNetworkConstants.AccessNetworkType.class;
+ for (Field field : clazz.getDeclaredFields()) {
+ final int modifiers = field.getModifiers();
+ if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+ && field.getType().equals(int.class)) {
+ boolean found = false;
+ int value = field.getInt(null);
+ for (int i = 0; i < MobileRadioPowerStatsCollector.NETWORK_TYPES.length; i++) {
+ if (MobileRadioPowerStatsCollector.NETWORK_TYPES[i] == value) {
+ found = true;
+ break;
+ }
+ }
+ assertWithMessage("New network type, " + field.getName() + " not represented in "
+ + MobileRadioPowerStatsCollector.class).that(found).isTrue();
+ }
+ }
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
new file mode 100644
index 000000000000..4ac7ad8d07ff
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
@@ -0,0 +1,499 @@
+/*
+ * 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.power.stats;
+
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.net.NetworkStats;
+import android.os.BatteryConsumer;
+import android.os.Handler;
+import android.os.OutcomeReceiver;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.telephony.ModemActivityInfo;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.function.IntSupplier;
+import java.util.function.LongSupplier;
+import java.util.function.Supplier;
+
+public class MobileRadioPowerStatsProcessorTest {
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
+ private static final double PRECISION = 0.00001;
+ private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
+ private static final int MOBILE_RADIO_ENERGY_CONSUMER_ID = 1;
+ private static final int VOLTAGE_MV = 3500;
+
+ @Rule(order = 1)
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
+ @Mock
+ private Context mContext;
+ @Mock
+ private PowerStatsUidResolver mPowerStatsUidResolver;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ @Mock
+ private Supplier<NetworkStats> mNetworkStatsSupplier;
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private LongSupplier mCallDurationSupplier;
+ @Mock
+ private LongSupplier mScanDurationSupplier;
+
+ private final MobileRadioPowerStatsCollector.Injector mInjector =
+ new MobileRadioPowerStatsCollector.Injector() {
+ @Override
+ public Handler getHandler() {
+ return mStatsRule.getHandler();
+ }
+
+ @Override
+ public Clock getClock() {
+ return mStatsRule.getMockClock();
+ }
+
+ @Override
+ public PowerStatsUidResolver getUidResolver() {
+ return mPowerStatsUidResolver;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ @Override
+ public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+ return mConsumedEnergyRetriever;
+ }
+
+ @Override
+ public IntSupplier getVoltageSupplier() {
+ return () -> VOLTAGE_MV;
+ }
+
+ @Override
+ public Supplier<NetworkStats> getMobileNetworkStatsSupplier() {
+ return mNetworkStatsSupplier;
+ }
+
+ @Override
+ public TelephonyManager getTelephonyManager() {
+ return mTelephonyManager;
+ }
+
+ @Override
+ public LongSupplier getCallDurationSupplier() {
+ return mCallDurationSupplier;
+ }
+
+ @Override
+ public LongSupplier getPhoneSignalScanDurationSupplier() {
+ return mScanDurationSupplier;
+ }
+ };
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
+ when(mPowerStatsUidResolver.mapUid(anyInt()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+ }
+
+ @Test
+ public void powerProfileModel() {
+ // No power monitoring hardware
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO))
+ .thenReturn(new int[0]);
+
+ mStatsRule.setTestPowerProfile("power_profile_test_modem_calculator");
+
+ MobileRadioPowerStatsProcessor processor =
+ new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile());
+
+ AggregatedPowerStatsConfig.PowerComponent config =
+ new AggregatedPowerStatsConfig.PowerComponent(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
+ .setProcessor(processor);
+
+ PowerComponentAggregatedPowerStats aggregatedStats =
+ new PowerComponentAggregatedPowerStats(
+ new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
+
+ aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+ aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
+ aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+
+ MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+ collector.setEnabled(true);
+
+ // Initial empty ModemActivityInfo.
+ mockModemActivityInfo(new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L));
+
+ // Establish a baseline
+ aggregatedStats.addPowerStats(collector.collectStats(), 0);
+
+ // Turn the screen off after 2.5 seconds
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+ aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+ aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+ 5000);
+
+ // Note application network activity
+ NetworkStats networkStats = mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 10000, 1500, 20000, 300, 100),
+ mockNetworkStatsEntry("cellular", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 5000, 500, 3000, 100, 111));
+
+ when(mNetworkStatsSupplier.get()).thenReturn(networkStats);
+
+ ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+ new int[]{100, 200, 300, 400, 500}, 600);
+ mockModemActivityInfo(mai);
+
+ when(mCallDurationSupplier.getAsLong()).thenReturn(200L);
+ when(mScanDurationSupplier.getAsLong()).thenReturn(5555L);
+
+ mStatsRule.setTime(10_000, 10_000);
+
+ PowerStats powerStats = collector.collectStats();
+
+ aggregatedStats.addPowerStats(powerStats, 10_000);
+
+ processor.finish(aggregatedStats);
+
+ MobileRadioPowerStatsLayout statsLayout =
+ new MobileRadioPowerStatsLayout(
+ aggregatedStats.getPowerStatsDescriptor());
+
+ // 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
+ // + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
+ // + 1440 mA * 300 ms (level 2 TX drain rate * level 2 TX duration)
+ // + 1800 mA * 400 ms (level 3 TX drain rate * level 3 TX duration)
+ // + 2160 mA * 500 ms (level 4 TX drain rate * level 4 TX duration)
+ // + 1440 mA * 600 ms (RX drain rate * RX duration)
+ // + 360 mA * 3000 ms (idle drain rate * idle duration)
+ // + 70 mA * 2000 ms (sleep drain rate * sleep duration)
+ // _________________
+ // = 4604000 mA-ms or 1.27888 mA-h
+ // 25% of 1.27888 = 0.319722
+ // 75% of 1.27888 = 0.959166
+ double totalPower = 0;
+ long[] deviceStats = new long[aggregatedStats.getPowerStatsDescriptor().statsArrayLength];
+ aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.319722);
+ totalPower += statsLayout.getDevicePowerEstimate(deviceStats);
+
+ aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.959166);
+ totalPower += statsLayout.getDevicePowerEstimate(deviceStats);
+
+ assertThat(totalPower).isWithin(PRECISION).of(1.27888);
+
+ // 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
+ // + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
+ // + 1440 mA * 300 ms (level 2 TX drain rate * level 2 TX duration)
+ // + 1800 mA * 400 ms (level 3 TX drain rate * level 3 TX duration)
+ // + 2160 mA * 500 ms (level 4 TX drain rate * level 4 TX duration)
+ // + 1440 mA * 600 ms (RX drain rate * RX duration)
+ // _________________
+ // = 3384000 mA-ms or 0.94 mA-h
+ double uidPower1 = 0;
+ long[] uidStats = new long[aggregatedStats.getPowerStatsDescriptor().uidStatsArrayLength];
+ aggregatedStats.getUidStats(uidStats, APP_UID,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.17625);
+ uidPower1 += statsLayout.getUidPowerEstimate(uidStats);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.17625);
+ uidPower1 += statsLayout.getUidPowerEstimate(uidStats);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.3525);
+ uidPower1 += statsLayout.getUidPowerEstimate(uidStats);
+
+ double uidPower2 = 0;
+ aggregatedStats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.05875);
+ uidPower2 += statsLayout.getUidPowerEstimate(uidStats);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.17625);
+ uidPower2 += statsLayout.getUidPowerEstimate(uidStats);
+
+ assertThat(uidPower1 + uidPower2)
+ .isWithin(PRECISION).of(0.94);
+
+ // 3/4 of total packets were sent by APP_UID so 75% of total
+ assertThat(uidPower1 / (uidPower1 + uidPower2))
+ .isWithin(PRECISION).of(0.75);
+ }
+
+ @Test
+ public void measuredEnergyModel() {
+ // PowerStats hardware is available
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO))
+ .thenReturn(new int[] {MOBILE_RADIO_ENERGY_CONSUMER_ID});
+
+ mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem")
+ .initMeasuredEnergyStatsLocked();
+
+ MobileRadioPowerStatsProcessor processor =
+ new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile());
+
+ AggregatedPowerStatsConfig.PowerComponent config =
+ new AggregatedPowerStatsConfig.PowerComponent(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
+ .setProcessor(processor);
+
+ PowerComponentAggregatedPowerStats aggregatedStats =
+ new PowerComponentAggregatedPowerStats(
+ new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
+
+ aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+ aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
+ aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+
+ MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+ collector.setEnabled(true);
+
+ // Initial empty ModemActivityInfo.
+ mockModemActivityInfo(new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L));
+
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(
+ new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID}))
+ .thenReturn(new long[]{0});
+
+ // Establish a baseline
+ aggregatedStats.addPowerStats(collector.collectStats(), 0);
+
+ // Turn the screen off after 2.5 seconds
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+ aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+ aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+ 5000);
+
+ // Note application network activity
+ NetworkStats networkStats = mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 10000, 1500, 20000, 300, 100),
+ mockNetworkStatsEntry("cellular", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 5000, 500, 3000, 100, 111));
+
+ when(mNetworkStatsSupplier.get()).thenReturn(networkStats);
+
+ ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+ new int[]{100, 200, 300, 400, 500}, 600);
+ mockModemActivityInfo(mai);
+
+ mStatsRule.setTime(10_000, 10_000);
+
+ long energyUws = 10_000_000L * VOLTAGE_MV / 1000L;
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(
+ new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID})).thenReturn(new long[]{energyUws});
+
+ when(mCallDurationSupplier.getAsLong()).thenReturn(200L);
+ when(mScanDurationSupplier.getAsLong()).thenReturn(5555L);
+
+ PowerStats powerStats = collector.collectStats();
+
+ aggregatedStats.addPowerStats(powerStats, 10_000);
+
+ processor.finish(aggregatedStats);
+
+ MobileRadioPowerStatsLayout statsLayout =
+ new MobileRadioPowerStatsLayout(
+ aggregatedStats.getPowerStatsDescriptor());
+
+ // 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh
+ double totalPower = 0;
+ long[] deviceStats = new long[aggregatedStats.getPowerStatsDescriptor().statsArrayLength];
+ aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.671837);
+ totalPower += statsLayout.getDevicePowerEstimate(deviceStats);
+ assertThat(statsLayout.getDeviceCallPowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.022494);
+ totalPower += statsLayout.getDeviceCallPowerEstimate(deviceStats);
+
+ aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(2.01596);
+ totalPower += statsLayout.getDevicePowerEstimate(deviceStats);
+ assertThat(statsLayout.getDeviceCallPowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.067484);
+ totalPower += statsLayout.getDeviceCallPowerEstimate(deviceStats);
+
+ // These estimates are supposed to add up to the measured energy, 2.77778 mAh
+ assertThat(totalPower).isWithin(PRECISION).of(2.77778);
+
+ double uidPower1 = 0;
+ long[] uidStats = new long[aggregatedStats.getPowerStatsDescriptor().uidStatsArrayLength];
+ aggregatedStats.getUidStats(uidStats, APP_UID,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.198236);
+ uidPower1 += statsLayout.getUidPowerEstimate(uidStats);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.198236);
+ uidPower1 += statsLayout.getUidPowerEstimate(uidStats);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.396473);
+ uidPower1 += statsLayout.getUidPowerEstimate(uidStats);
+
+ double uidPower2 = 0;
+ aggregatedStats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.066078);
+ uidPower2 += statsLayout.getUidPowerEstimate(uidStats);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.198236);
+ uidPower2 += statsLayout.getUidPowerEstimate(uidStats);
+
+ // Total power attributed to apps is significantly less than the grand total,
+ // because we only attribute TX/RX to apps but not maintaining a connection with the cell.
+ assertThat(uidPower1 + uidPower2)
+ .isWithin(PRECISION).of(1.057259);
+
+ // 3/4 of total packets were sent by APP_UID so 75% of total RX/TX power is attributed to it
+ assertThat(uidPower1 / (uidPower1 + uidPower2))
+ .isWithin(PRECISION).of(0.75);
+ }
+
+ private int[] states(int... states) {
+ return states;
+ }
+
+ private void mockModemActivityInfo(ModemActivityInfo emptyMai) {
+ doAnswer(invocation -> {
+ OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException>
+ receiver = invocation.getArgument(1);
+ receiver.onResult(emptyMai);
+ return null;
+ }).when(mTelephonyManager).requestModemActivityInfo(any(), any());
+ }
+
+ private NetworkStats mockNetworkStats(int elapsedTime, int initialSize,
+ NetworkStats.Entry... entries) {
+ NetworkStats stats;
+ if (RavenwoodRule.isOnRavenwood()) {
+ stats = mock(NetworkStats.class);
+ when(stats.iterator()).thenAnswer(inv -> List.of(entries).iterator());
+ } else {
+ stats = new NetworkStats(elapsedTime, initialSize);
+ for (NetworkStats.Entry entry : entries) {
+ stats = stats.addEntry(entry);
+ }
+ }
+ return stats;
+ }
+
+ private static NetworkStats.Entry mockNetworkStatsEntry(@Nullable String iface, int uid,
+ int set, int tag, int metered, int roaming, int defaultNetwork, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, long operations) {
+ if (RavenwoodRule.isOnRavenwood()) {
+ NetworkStats.Entry entry = mock(NetworkStats.Entry.class);
+ when(entry.getUid()).thenReturn(uid);
+ when(entry.getMetered()).thenReturn(metered);
+ when(entry.getRoaming()).thenReturn(roaming);
+ when(entry.getDefaultNetwork()).thenReturn(defaultNetwork);
+ when(entry.getRxBytes()).thenReturn(rxBytes);
+ when(entry.getRxPackets()).thenReturn(rxPackets);
+ when(entry.getTxBytes()).thenReturn(txBytes);
+ when(entry.getTxPackets()).thenReturn(txPackets);
+ when(entry.getOperations()).thenReturn(operations);
+ return entry;
+ } else {
+ return new NetworkStats.Entry(iface, uid, set, tag, metered,
+ roaming, defaultNetwork, rxBytes, rxPackets, txBytes, txPackets, operations);
+ }
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 9f069130502f..da3834633552 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -52,6 +52,8 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
// The mNetworkStats will be used for both wifi and mobile categories
private NetworkStats mNetworkStats;
private DummyExternalStatsSync mExternalStatsSync = new DummyExternalStatsSync();
+ public static final BatteryStatsConfig DEFAULT_CONFIG =
+ new BatteryStatsConfig.Builder().build();
MockBatteryStatsImpl() {
this(new MockClock());
@@ -66,12 +68,12 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
}
MockBatteryStatsImpl(Clock clock, File historyDirectory, Handler handler) {
- this(clock, historyDirectory, handler, new PowerStatsUidResolver());
+ this(DEFAULT_CONFIG, clock, historyDirectory, handler, new PowerStatsUidResolver());
}
- MockBatteryStatsImpl(Clock clock, File historyDirectory, Handler handler,
- PowerStatsUidResolver powerStatsUidResolver) {
- super(clock, historyDirectory, handler, powerStatsUidResolver,
+ MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory,
+ Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
+ super(config, clock, historyDirectory, handler, powerStatsUidResolver,
mock(FrameworkStatsLogger.class), mock(BatteryStatsHistory.TraceDelegate.class),
mock(BatteryStatsHistory.EventLogger.class));
initTimersAndCounters();
@@ -276,10 +278,6 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
public void writeSyncLocked() {
}
- public void setHandler(Handler handler) {
- mHandler = handler;
- }
-
@Override
protected void updateBatteryPropertiesLocked() {
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
new file mode 100644
index 000000000000..dadcf3f3871e
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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.power.stats;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.net.NetworkStats;
+import android.os.BatteryConsumer;
+import android.os.Handler;
+import android.os.OutcomeReceiver;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.telephony.ModemActivityInfo;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.os.Clock;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.IntSupplier;
+import java.util.function.LongSupplier;
+import java.util.function.Supplier;
+
+public class PhoneCallPowerStatsProcessorTest {
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
+ private static final double PRECISION = 0.00001;
+ private static final int VOLTAGE_MV = 3500;
+
+ @Rule(order = 1)
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
+ @Mock
+ private Context mContext;
+ @Mock
+ private PowerStatsUidResolver mPowerStatsUidResolver;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ @Mock
+ private Supplier<NetworkStats> mNetworkStatsSupplier;
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private LongSupplier mCallDurationSupplier;
+ @Mock
+ private LongSupplier mScanDurationSupplier;
+
+ private final MobileRadioPowerStatsCollector.Injector mInjector =
+ new MobileRadioPowerStatsCollector.Injector() {
+ @Override
+ public Handler getHandler() {
+ return mStatsRule.getHandler();
+ }
+
+ @Override
+ public Clock getClock() {
+ return mStatsRule.getMockClock();
+ }
+
+ @Override
+ public PowerStatsUidResolver getUidResolver() {
+ return mPowerStatsUidResolver;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ @Override
+ public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+ return mConsumedEnergyRetriever;
+ }
+
+ @Override
+ public IntSupplier getVoltageSupplier() {
+ return () -> VOLTAGE_MV;
+ }
+
+ @Override
+ public Supplier<NetworkStats> getMobileNetworkStatsSupplier() {
+ return mNetworkStatsSupplier;
+ }
+
+ @Override
+ public TelephonyManager getTelephonyManager() {
+ return mTelephonyManager;
+ }
+
+ @Override
+ public LongSupplier getCallDurationSupplier() {
+ return mCallDurationSupplier;
+ }
+
+ @Override
+ public LongSupplier getPhoneSignalScanDurationSupplier() {
+ return mScanDurationSupplier;
+ }
+ };
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
+ when(mPowerStatsUidResolver.mapUid(anyInt()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ // No power monitoring hardware
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO))
+ .thenReturn(new int[0]);
+
+ mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem");
+ }
+
+ @Test
+ public void copyEstimatesFromMobileRadioPowerStats() {
+ MobileRadioPowerStatsProcessor mobileStatsProcessor =
+ new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile());
+
+ PhoneCallPowerStatsProcessor phoneStatsProcessor =
+ new PhoneCallPowerStatsProcessor();
+
+ AggregatedPowerStatsConfig aggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
+ aggregatedPowerStatsConfig.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
+ .setProcessor(mobileStatsProcessor);
+ aggregatedPowerStatsConfig.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_PHONE,
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+ .setProcessor(phoneStatsProcessor);
+
+ AggregatedPowerStats aggregatedPowerStats =
+ new AggregatedPowerStats(aggregatedPowerStatsConfig);
+ PowerComponentAggregatedPowerStats mobileRadioStats =
+ aggregatedPowerStats.getPowerComponentStats(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO);
+
+ aggregatedPowerStats.setDeviceState(STATE_POWER, POWER_STATE_OTHER, 0);
+ aggregatedPowerStats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+
+ MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+ collector.setEnabled(true);
+
+ // Initial empty ModemActivityInfo.
+ mockModemActivityInfo(new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L));
+
+ // Establish a baseline
+ aggregatedPowerStats.addPowerStats(collector.collectStats(), 0);
+
+ // Turn the screen off after 2.5 seconds
+ aggregatedPowerStats.setDeviceState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+
+ ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+ new int[]{100, 200, 300, 400, 500}, 600);
+ mockModemActivityInfo(mai);
+
+ // A phone call was made
+ when(mCallDurationSupplier.getAsLong()).thenReturn(7000L);
+
+ mStatsRule.setTime(10_000, 10_000);
+
+ aggregatedPowerStats.addPowerStats(collector.collectStats(), 10_000);
+
+ mobileStatsProcessor.finish(mobileRadioStats);
+
+ PowerComponentAggregatedPowerStats stats =
+ aggregatedPowerStats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_PHONE);
+ phoneStatsProcessor.finish(stats);
+
+ PowerStatsLayout statsLayout =
+ new PowerStatsLayout(stats.getPowerStatsDescriptor());
+
+ long[] deviceStats = new long[stats.getPowerStatsDescriptor().statsArrayLength];
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.7);
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(2.1);
+ }
+
+ private void mockModemActivityInfo(ModemActivityInfo emptyMai) {
+ doAnswer(invocation -> {
+ OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException>
+ receiver = invocation.getArgument(1);
+ receiver.onResult(emptyMai);
+ return null;
+ }).when(mTelephonyManager).requestModemActivityInfo(any(), any());
+ }
+
+ private int[] states(int... states) {
+ return states;
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
index 2ea86a4527eb..03b02cfde146 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
@@ -76,9 +76,8 @@ public class PowerStatsAggregatorTest {
@Test
public void stateUpdates() {
- PowerStats.Descriptor descriptor =
- new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
- new PersistableBundle());
+ PowerStats.Descriptor descriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT,
+ "majorDrain", 1, null, 0, 1, new PersistableBundle());
PowerStats powerStats = new PowerStats(descriptor);
mClock.currentTime = 1222156800000L; // An important date in world history
@@ -186,9 +185,8 @@ public class PowerStatsAggregatorTest {
@Test
public void incompatiblePowerStats() {
- PowerStats.Descriptor descriptor =
- new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
- new PersistableBundle());
+ PowerStats.Descriptor descriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT,
+ "majorDrain", 1, null, 0, 1, new PersistableBundle());
PowerStats powerStats = new PowerStats(descriptor);
mHistory.forceRecordAllHistory();
@@ -209,7 +207,7 @@ public class PowerStatsAggregatorTest {
advance(1000);
- descriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
+ descriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, null, 0, 1,
PersistableBundle.forPair("something", "changed"));
powerStats = new PowerStats(descriptor);
powerStats.stats = new long[]{20000};
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
index 17a7d3ecf9d3..df1200bb6b1a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
@@ -18,11 +18,22 @@ package com.android.server.power.stats;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.PersistableBundle;
+import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import android.power.PowerStatsInternal;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -34,6 +45,9 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class PowerStatsCollectorTest {
@@ -57,7 +71,8 @@ public class PowerStatsCollectorTest {
mMockClock) {
@Override
protected PowerStats collectStats() {
- return new PowerStats(new PowerStats.Descriptor(0, 0, 0, new PersistableBundle()));
+ return new PowerStats(
+ new PowerStats.Descriptor(0, 0, null, 0, 0, new PersistableBundle()));
}
};
mCollector.addConsumer(stats -> mCollectedStats = stats);
@@ -92,4 +107,74 @@ public class PowerStatsCollectorTest {
mHandler.post(done::open);
done.block();
}
+
+ @Test
+ @DisabledOnRavenwood
+ public void consumedEnergyRetriever() throws Exception {
+ PowerStatsInternal powerStatsInternal = mock(PowerStatsInternal.class);
+ mockEnergyConsumers(powerStatsInternal);
+
+ PowerStatsCollector.ConsumedEnergyRetrieverImpl retriever =
+ new PowerStatsCollector.ConsumedEnergyRetrieverImpl(powerStatsInternal);
+ int[] energyConsumerIds = retriever.getEnergyConsumerIds(EnergyConsumerType.CPU_CLUSTER);
+ assertThat(energyConsumerIds).isEqualTo(new int[]{1, 2});
+ long[] energy = retriever.getConsumedEnergyUws(energyConsumerIds);
+ assertThat(energy).isEqualTo(new long[]{1000, 2000});
+ energy = retriever.getConsumedEnergyUws(energyConsumerIds);
+ assertThat(energy).isEqualTo(new long[]{1500, 2700});
+ }
+
+ @SuppressWarnings("unchecked")
+ private void mockEnergyConsumers(PowerStatsInternal powerStatsInternal) throws Exception {
+ when(powerStatsInternal.getEnergyConsumerInfo())
+ .thenReturn(new EnergyConsumer[]{
+ new EnergyConsumer() {{
+ id = 1;
+ type = EnergyConsumerType.CPU_CLUSTER;
+ ordinal = 0;
+ name = "CPU0";
+ }},
+ new EnergyConsumer() {{
+ id = 2;
+ type = EnergyConsumerType.CPU_CLUSTER;
+ ordinal = 1;
+ name = "CPU4";
+ }},
+ new EnergyConsumer() {{
+ id = 3;
+ type = EnergyConsumerType.BLUETOOTH;
+ name = "BT";
+ }},
+ });
+
+ CompletableFuture<EnergyConsumerResult[]> future1 = mock(CompletableFuture.class);
+ when(future1.get(anyLong(), any(TimeUnit.class)))
+ .thenReturn(new EnergyConsumerResult[]{
+ new EnergyConsumerResult() {{
+ id = 1;
+ energyUWs = 1000;
+ }},
+ new EnergyConsumerResult() {{
+ id = 2;
+ energyUWs = 2000;
+ }}
+ });
+
+ CompletableFuture<EnergyConsumerResult[]> future2 = mock(CompletableFuture.class);
+ when(future2.get(anyLong(), any(TimeUnit.class)))
+ .thenReturn(new EnergyConsumerResult[]{
+ new EnergyConsumerResult() {{
+ id = 1;
+ energyUWs = 1500;
+ }},
+ new EnergyConsumerResult() {{
+ id = 2;
+ energyUWs = 2700;
+ }}
+ });
+
+ when(powerStatsInternal.getEnergyConsumedAsync(eq(new int[]{1, 2})))
+ .thenReturn(future1)
+ .thenReturn(future2);
+ }
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
index 18d7b909150b..412fc88dcd27 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
@@ -77,7 +77,7 @@ public class PowerStatsExporterTest {
private PowerStatsStore mPowerStatsStore;
private PowerStatsAggregator mPowerStatsAggregator;
private BatteryStatsHistory mHistory;
- private CpuPowerStatsCollector.CpuStatsArrayLayout mCpuStatsArrayLayout;
+ private CpuPowerStatsLayout mCpuStatsArrayLayout;
private PowerStats.Descriptor mPowerStatsDescriptor;
@Before
@@ -93,7 +93,7 @@ public class PowerStatsExporterTest {
AggregatedPowerStatsConfig.STATE_SCREEN,
AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
.setProcessor(
- new CpuAggregatedPowerStatsProcessor(mStatsRule.getPowerProfile(),
+ new CpuPowerStatsProcessor(mStatsRule.getPowerProfile(),
mStatsRule.getCpuScalingPolicies()));
mPowerStatsStore = new PowerStatsStore(storeDirectory, new TestHandler(), config);
@@ -102,9 +102,10 @@ public class PowerStatsExporterTest {
mMonotonicClock, null, null);
mPowerStatsAggregator = new PowerStatsAggregator(config, mHistory);
- mCpuStatsArrayLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout();
+ mCpuStatsArrayLayout = new CpuPowerStatsLayout();
mCpuStatsArrayLayout.addDeviceSectionCpuTimeByScalingStep(1);
mCpuStatsArrayLayout.addDeviceSectionCpuTimeByCluster(1);
+ mCpuStatsArrayLayout.addDeviceSectionUsageDuration();
mCpuStatsArrayLayout.addDeviceSectionPowerEstimate();
mCpuStatsArrayLayout.addUidSectionCpuTimeByPowerBracket(new int[]{0});
mCpuStatsArrayLayout.addUidSectionPowerEstimate();
@@ -113,7 +114,7 @@ public class PowerStatsExporterTest {
mPowerStatsDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU,
mCpuStatsArrayLayout.getDeviceStatsArrayLength(),
- mCpuStatsArrayLayout.getUidStatsArrayLength(), extras);
+ null, 0, mCpuStatsArrayLayout.getUidStatsArrayLength(), extras);
}
@Test
@@ -126,20 +127,20 @@ public class PowerStatsExporterTest {
BatteryUsageStats actual = builder.build();
String message = "Actual BatteryUsageStats: " + actual;
- assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53);
- assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53);
+ assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 7.51016);
+ assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 7.51016);
assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_ANY, 13.5);
+ BatteryConsumer.PROCESS_STATE_ANY, 3.97099);
assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_FOREGROUND, 7.47);
+ BatteryConsumer.PROCESS_STATE_FOREGROUND, 2.198082);
assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_BACKGROUND, 6.03);
+ BatteryConsumer.PROCESS_STATE_BACKGROUND, 1.772916);
assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_ANY, 12.03);
+ BatteryConsumer.PROCESS_STATE_ANY, 3.538999);
assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 12.03);
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 3.538999);
actual.close();
}
@@ -154,20 +155,20 @@ public class PowerStatsExporterTest {
BatteryUsageStats actual = builder.build();
String message = "Actual BatteryUsageStats: " + actual;
- assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 15.4);
- assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 15.4);
+ assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 4.526749);
+ assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 4.526749);
assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_ANY, 4.06);
+ BatteryConsumer.PROCESS_STATE_ANY, 1.193332);
assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_FOREGROUND, 1.35);
+ BatteryConsumer.PROCESS_STATE_FOREGROUND, 0.397749);
assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_BACKGROUND, 2.70);
+ BatteryConsumer.PROCESS_STATE_BACKGROUND, 0.795583);
assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_ANY, 11.33);
+ BatteryConsumer.PROCESS_STATE_ANY, 3.333249);
assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 11.33);
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 3.333249);
actual.close();
}
@@ -182,13 +183,13 @@ public class PowerStatsExporterTest {
BatteryUsageStats actual = builder.build();
String message = "Actual BatteryUsageStats: " + actual;
- assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53);
- assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53);
+ assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 7.51016);
+ assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 7.51016);
assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_ANY, 13.5);
+ BatteryConsumer.PROCESS_STATE_ANY, 3.97099);
assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU,
- BatteryConsumer.PROCESS_STATE_ANY, 12.03);
+ BatteryConsumer.PROCESS_STATE_ANY, 3.538999);
UidBatteryConsumer uidScope = actual.getUidBatteryConsumers().stream()
.filter(us -> us.getUid() == APP_UID1).findFirst().orElse(null);
// There shouldn't be any per-procstate data
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsProcessorTest.java
index af83be04db7d..02e446aa1859 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsProcessorTest.java
@@ -35,7 +35,7 @@ import java.util.Objects;
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class AggregatedPowerStatsProcessorTest {
+public class PowerStatsProcessorTest {
@Test
public void createPowerEstimationPlan_allDeviceStatesPresentInUidStates() {
@@ -44,8 +44,8 @@ public class AggregatedPowerStatsProcessorTest {
.trackDeviceStates(STATE_POWER, STATE_SCREEN)
.trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE);
- AggregatedPowerStatsProcessor.PowerEstimationPlan plan =
- new AggregatedPowerStatsProcessor.PowerEstimationPlan(config);
+ PowerStatsProcessor.PowerEstimationPlan plan =
+ new PowerStatsProcessor.PowerEstimationPlan(config);
assertThat(deviceStateEstimatesToStrings(plan))
.containsExactly("[0, 0]", "[0, 1]", "[1, 0]", "[1, 1]");
assertThat(combinedDeviceStatsToStrings(plan))
@@ -65,8 +65,8 @@ public class AggregatedPowerStatsProcessorTest {
.trackDeviceStates(STATE_POWER, STATE_SCREEN)
.trackUidStates(STATE_POWER, STATE_PROCESS_STATE);
- AggregatedPowerStatsProcessor.PowerEstimationPlan plan =
- new AggregatedPowerStatsProcessor.PowerEstimationPlan(config);
+ PowerStatsProcessor.PowerEstimationPlan plan =
+ new PowerStatsProcessor.PowerEstimationPlan(config);
assertThat(deviceStateEstimatesToStrings(plan))
.containsExactly("[0, 0]", "[0, 1]", "[1, 0]", "[1, 1]");
@@ -81,13 +81,13 @@ public class AggregatedPowerStatsProcessorTest {
}
private static List<String> deviceStateEstimatesToStrings(
- AggregatedPowerStatsProcessor.PowerEstimationPlan plan) {
+ PowerStatsProcessor.PowerEstimationPlan plan) {
return plan.deviceStateEstimations.stream()
.map(dse -> dse.stateValues).map(Arrays::toString).toList();
}
private static List<String> combinedDeviceStatsToStrings(
- AggregatedPowerStatsProcessor.PowerEstimationPlan plan) {
+ PowerStatsProcessor.PowerEstimationPlan plan) {
return plan.combinedDeviceStateEstimations.stream()
.map(cds -> cds.deviceStateEstimations)
.map(dses -> dses.stream()
@@ -97,7 +97,7 @@ public class AggregatedPowerStatsProcessorTest {
}
private static List<String> uidStateEstimatesToStrings(
- AggregatedPowerStatsProcessor.PowerEstimationPlan plan,
+ PowerStatsProcessor.PowerEstimationPlan plan,
AggregatedPowerStatsConfig.PowerComponent config) {
MultiStateStats.States[] uidStateConfig = config.getUidStateConfig();
return plan.uidStateEstimates.stream()
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java
index 80cbe0da402e..d67d40862e95 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java
@@ -18,11 +18,14 @@ package com.android.server.power.stats;
import static com.google.common.truth.Truth.assertThat;
+import android.platform.test.ravenwood.RavenwoodRule;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.KernelSingleProcessCpuThreadReader;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -30,7 +33,10 @@ import java.io.IOException;
@SmallTest
@RunWith(AndroidJUnit4.class)
+@android.platform.test.annotations.DisabledOnRavenwood(reason = "Kernel dependency")
public class SystemServerCpuThreadReaderTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
@Test
public void testReadDelta() throws IOException {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
index 8e53d5285cc4..ef0b570a1354 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
@@ -31,9 +31,10 @@ import android.os.Process;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.RavenwoodFlagsValueProvider;
+import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.KernelCpuSpeedReader;
@@ -46,7 +47,6 @@ import com.android.server.power.optimization.Flags;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -55,17 +55,21 @@ import java.util.ArrayList;
import java.util.Collection;
@SmallTest
-@RunWith(AndroidJUnit4.class)
@SuppressWarnings("GuardedBy")
public class SystemServicePowerCalculatorTest {
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ @Rule(order = 1)
+ public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood()
+ ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
+ : DeviceFlagsValueProvider.createCheckFlagsRule();
private static final double PRECISION = 0.000001;
private static final int APP_UID1 = 100;
private static final int APP_UID2 = 200;
- @Rule
+ @Rule(order = 2)
public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
.setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
.setCpuScalingPolicy(0, new int[]{0, 1}, new int[]{100, 200})
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index e18909879934..0c92abce7254 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -26,6 +26,7 @@ import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.nullable;
import static org.mockito.Mockito.times;
@@ -45,6 +46,7 @@ import android.app.PropertyInvalidatedCache;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -57,6 +59,7 @@ import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.database.Cursor;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
@@ -155,6 +158,8 @@ public class AccountManagerServiceTest extends AndroidTestCase {
mPackageInfo.applicationInfo = new ApplicationInfo();
mPackageInfo.applicationInfo.privateFlags = ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo);
+ when(mMockPackageManager.getPackageInfoAsUser(
+ anyString(), anyInt(), anyInt())).thenReturn(mPackageInfo);
when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOpsManager);
when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
when(mMockContext.getSystemServiceName(AppOpsManager.class)).thenReturn(
@@ -3189,6 +3194,78 @@ public class AccountManagerServiceTest extends AndroidTestCase {
}
@SmallTest
+ public void testAccountsChangedBroadcastMarkedAccountAsVisibleThreeTimes() throws Exception {
+ unlockSystemUser();
+
+ HashMap<String, Integer> visibility = new HashMap<>();
+ visibility.put("testpackage1", AccountManager.VISIBILITY_VISIBLE);
+
+ addAccountRemovedReceiver("testpackage1");
+ mAms.registerAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage1");
+ mAms.addAccountExplicitlyWithVisibility(
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ /* password= */ "p11",
+ /* extras= */ null,
+ visibility,
+ /* callerPackage= */ null);
+
+ updateBroadcastCounters(2);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 1);
+ assertEquals(mLoginAccountsChangedBroadcasts, 1);
+ assertEquals(mAccountRemovedBroadcasts, 0);
+
+ mAms.setAccountVisibility(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "testpackage1",
+ AccountManager.VISIBILITY_VISIBLE);
+ mAms.setAccountVisibility(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "testpackage1",
+ AccountManager.VISIBILITY_VISIBLE);
+
+ updateBroadcastCounters(2);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 1);
+ assertEquals(mLoginAccountsChangedBroadcasts, 1);
+ assertEquals(mAccountRemovedBroadcasts, 0);
+ }
+
+ @SmallTest
+ public void testAccountsChangedBroadcastChangedVisibilityTwoTimes() throws Exception {
+ unlockSystemUser();
+
+ HashMap<String, Integer> visibility = new HashMap<>();
+ visibility.put("testpackage1", AccountManager.VISIBILITY_VISIBLE);
+
+ addAccountRemovedReceiver("testpackage1");
+ mAms.registerAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage1");
+ mAms.addAccountExplicitlyWithVisibility(
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ /* password= */ "p11",
+ /* extras= */ null,
+ visibility,
+ /* callerPackage= */ null);
+
+ updateBroadcastCounters(2);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 1);
+ assertEquals(mLoginAccountsChangedBroadcasts, 1);
+ assertEquals(mAccountRemovedBroadcasts, 0);
+
+ mAms.setAccountVisibility(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "testpackage1",
+ AccountManager.VISIBILITY_NOT_VISIBLE);
+ mAms.setAccountVisibility(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "testpackage1",
+ AccountManager.VISIBILITY_VISIBLE);
+
+ updateBroadcastCounters(7);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 3);
+ assertEquals(mLoginAccountsChangedBroadcasts, 3);
+ assertEquals(mAccountRemovedBroadcasts, 1);
+ }
+
+ @SmallTest
public void testRegisterAccountListenerCredentialsUpdate() throws Exception {
unlockSystemUser();
mAms.registerAccountListener(
@@ -3586,6 +3663,19 @@ public class AccountManagerServiceTest extends AndroidTestCase {
}
@Override
+ public Resources getResources() {
+ Resources mockResources = mock(Resources.class);
+ // config_canRemoveFirstAccount = true
+ when(mockResources.getBoolean(anyInt())).thenReturn(true);
+ return mockResources;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mock(ContentResolver.class);
+ }
+
+ @Override
public void sendBroadcastAsUser(Intent intent, UserHandle user) {
sendBroadcastAsUser(intent, user, null, null);
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 0a2a855e9317..7c0dbf4889da 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -888,7 +888,7 @@ public class UserControllerTest {
int userId = -1;
assertThrows(IllegalArgumentException.class,
- () -> mUserController.stopUser(userId, /* force= */ true,
+ () -> mUserController.stopUser(userId,
/* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
/* keyEvictedCallback= */ null));
}
@@ -897,7 +897,7 @@ public class UserControllerTest {
public void testStopUser_systemUser() {
int userId = UserHandle.USER_SYSTEM;
- int r = mUserController.stopUser(userId, /* force= */ true,
+ int r = mUserController.stopUser(userId,
/* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
/* keyEvictedCallback= */ null);
@@ -909,7 +909,7 @@ public class UserControllerTest {
setUpUser(TEST_USER_ID1, /* flags= */ 0);
mUserController.startUser(TEST_USER_ID1, USER_START_MODE_FOREGROUND);
- int r = mUserController.stopUser(TEST_USER_ID1, /* force= */ true,
+ int r = mUserController.stopUser(TEST_USER_ID1,
/* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
/* keyEvictedCallback= */ null);
@@ -1338,7 +1338,7 @@ public class UserControllerTest {
private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean allowDelayedLocking,
KeyEvictedCallback keyEvictedCallback, boolean expectLocking) throws Exception {
- int r = mUserController.stopUser(userId, /* force= */ true, /* allowDelayedLocking= */
+ int r = mUserController.stopUser(userId, /* allowDelayedLocking= */
allowDelayedLocking, null, keyEvictedCallback);
assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS);
assertUserLockedOrUnlockedState(userId, allowDelayedLocking, expectLocking);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index 9f3f29742f5b..e4c56a7237d4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -176,6 +176,21 @@ public class FaceServiceTest {
}
@Test
+ @RequiresFlagsEnabled({Flags.FLAG_DE_HIDL, Flags.FLAG_FACE_VHAL_FEATURE})
+ public void registerAuthenticatorsLegacy_virtualFaceOnly() throws Exception {
+ initService();
+ Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+ Settings.Secure.BIOMETRIC_FACE_VIRTUAL_ENABLED, 1);
+
+ mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
+ eq(BiometricAuthenticator.TYPE_FACE),
+ eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
+ }
+
+ @Test
@RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
mFaceSensorConfigurations = new FaceSensorConfigurations(false);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 20961a9ddddd..9a8cd48fb4cf 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -223,6 +223,18 @@ public class FingerprintServiceTest {
}
@Test
+ public void registerAuthenticators_virtualFingerprintOnly() throws Exception {
+ initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+ Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+ Settings.Secure.BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED, 1);
+
+ mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+ }
+
+ @Test
@RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
diff --git a/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java b/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
index 6c5a56936e93..af6f6f2b6cc2 100644
--- a/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
+++ b/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
@@ -18,6 +18,7 @@ package com.android.server.grammaticalinflection;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -46,6 +47,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
+import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
@@ -67,7 +69,7 @@ public class GrammaticalInflectionBackupTest {
@Before
public void setUp() throws Exception {
mBackupHelper = new GrammaticalInflectionBackupHelper(
- mGrammaticalInflectionService, mMockPackageManager);
+ null, mGrammaticalInflectionService, mMockPackageManager);
}
@Test
@@ -106,6 +108,28 @@ public class GrammaticalInflectionBackupTest {
eq(Configuration.GRAMMATICAL_GENDER_NEUTRAL));
}
+ @Test
+ public void testSystemBackupPayload_returnsGender()
+ throws IOException, ClassNotFoundException {
+ doReturn(Configuration.GRAMMATICAL_GENDER_MASCULINE).when(mGrammaticalInflectionService)
+ .getSystemGrammaticalGender(any(), eq(DEFAULT_USER_ID));
+
+ int gender = convertByteArrayToInt(mBackupHelper.getSystemBackupPayload(DEFAULT_USER_ID));
+
+ assertEquals(gender, Configuration.GRAMMATICAL_GENDER_MASCULINE);
+ }
+
+ @Test
+ public void testApplySystemPayload_setSystemWideGrammaticalGender()
+ throws IOException {
+ mBackupHelper.applyRestoredSystemPayload(
+ intToByteArray(Configuration.GRAMMATICAL_GENDER_NEUTRAL), DEFAULT_USER_ID);
+
+ verify(mGrammaticalInflectionService).setSystemWideGrammaticalGender(
+ eq(Configuration.GRAMMATICAL_GENDER_NEUTRAL),
+ eq(DEFAULT_USER_ID));
+ }
+
private void mockAppInstalled() {
ApplicationInfo dummyApp = new ApplicationInfo();
dummyApp.packageName = DEFAULT_PACKAGE_NAME;
@@ -141,4 +165,15 @@ public class GrammaticalInflectionBackupTest {
}
return data;
}
+
+ private byte[] intToByteArray(final int gender) {
+ ByteBuffer bb = ByteBuffer.allocate(4);
+ bb.putInt(gender);
+ return bb.array();
+ }
+
+ private int convertByteArrayToInt(byte[] intBytes) {
+ ByteBuffer byteBuffer = ByteBuffer.wrap(intBytes);
+ return byteBuffer.getInt();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
index 43bf53714cba..72caae3694de 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
@@ -242,7 +242,7 @@ public class UserLifecycleStressTest {
private void stopUser(int userId) throws RemoteException, InterruptedException {
runWithLatch("stop user", countDownLatch -> {
ActivityManager.getService()
- .stopUser(userId, /* force= */ true, new IStopUserCallback.Stub() {
+ .stopUserWithCallback(userId, new IStopUserCallback.Stub() {
@Override
public void userStopped(int userId) {
countDownLatch.countDown();
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 4405a20cb059..b91ef7cbfff8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -1256,6 +1256,30 @@ public final class UserManagerTest {
@MediumTest
@Test
+ public void testDefaultUserRestrictionsForPrivateProfile() {
+ assumeTrue(mUserManager.canAddPrivateProfile());
+ final int currentUserId = ActivityManager.getCurrentUser();
+ UserInfo privateProfileInfo = null;
+ try {
+ privateProfileInfo = createProfileForUser("Private",
+ UserManager.USER_TYPE_PROFILE_PRIVATE, currentUserId);
+ assertThat(privateProfileInfo).isNotNull();
+ } catch (Exception e) {
+ fail("Creation of private profile failed due to " + e.getMessage());
+ }
+ assertDefaultPrivateProfileRestrictions(privateProfileInfo.getUserHandle());
+ }
+
+ private void assertDefaultPrivateProfileRestrictions(UserHandle userHandle) {
+ Bundle defaultPrivateProfileRestrictions =
+ UserTypeFactory.getDefaultPrivateProfileRestrictions();
+ for (String restriction : defaultPrivateProfileRestrictions.keySet()) {
+ assertThat(mUserManager.hasUserRestrictionForUser(restriction, userHandle)).isTrue();
+ }
+ }
+
+ @MediumTest
+ @Test
public void testAddRestrictedProfile() throws Exception {
if (isAutomotive() || UserManager.isHeadlessSystemUserMode()) return;
assertWithMessage("There should be no associated restricted profiles before the test")
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 610627886c1b..f08fbde962ef 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -7916,8 +7916,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setAppInForegroundForToasts(mUid, true);
// enqueue toast -> toast should still enqueue
- enqueueToast(testPackage, new TestableToastCallback());
+ boolean wasEnqueued = enqueueToast(testPackage, new TestableToastCallback());
assertEquals(1, mService.mToastQueue.size());
+ assertThat(wasEnqueued).isTrue();
}
@Test
@@ -7936,8 +7937,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setAppInForegroundForToasts(mUid, false);
// enqueue toast -> no toasts enqueued
- enqueueToast(testPackage, new TestableToastCallback());
+ boolean wasEnqueued = enqueueToast(testPackage, new TestableToastCallback());
assertEquals(0, mService.mToastQueue.size());
+ assertThat(wasEnqueued).isFalse();
}
@Test
@@ -8045,8 +8047,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setAppInForegroundForToasts(mUid, true);
// enqueue toast -> toast should still enqueue
- enqueueTextToast(testPackage, "Text");
+ boolean wasEnqueued = enqueueTextToast(testPackage, "Text");
assertEquals(1, mService.mToastQueue.size());
+ assertThat(wasEnqueued).isTrue();
}
@Test
@@ -8065,8 +8068,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setAppInForegroundForToasts(mUid, false);
// enqueue toast -> toast should still enqueue
- enqueueTextToast(testPackage, "Text");
+ boolean wasEnqueued = enqueueTextToast(testPackage, "Text");
assertEquals(1, mService.mToastQueue.size());
+ assertThat(wasEnqueued).isTrue();
}
@Test
@@ -8220,8 +8224,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setAppInForegroundForToasts(mUid, false);
// enqueue toast -> toast should still enqueue
- enqueueToast(testPackage, new TestableToastCallback());
+ boolean wasEnqueued = enqueueToast(testPackage, new TestableToastCallback());
assertEquals(1, mService.mToastQueue.size());
+ assertThat(wasEnqueued).isTrue();
verify(mAm).setProcessImportant(any(), anyInt(), eq(true), any());
}
@@ -8242,8 +8247,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setAppInForegroundForToasts(mUid, true);
// enqueue toast -> toast should still enqueue
- enqueueTextToast(testPackage, "Text");
+ boolean wasEnqueued = enqueueTextToast(testPackage, "Text");
assertEquals(1, mService.mToastQueue.size());
+ assertThat(wasEnqueued).isTrue();
verify(mAm).setProcessImportant(any(), anyInt(), eq(false), any());
}
@@ -8264,8 +8270,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setAppInForegroundForToasts(mUid, false);
// enqueue toast -> toast should still enqueue
- enqueueTextToast(testPackage, "Text");
+ boolean wasEnqueued = enqueueTextToast(testPackage, "Text");
assertEquals(1, mService.mToastQueue.size());
+ assertThat(wasEnqueued).isTrue();
verify(mAm).setProcessImportant(any(), anyInt(), eq(false), any());
}
@@ -8274,7 +8281,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
allowTestPackageToToast();
// enqueue toast -> no toasts enqueued
- enqueueTextToast(TEST_PACKAGE, "Text");
+ boolean wasEnqueued = enqueueTextToast(TEST_PACKAGE, "Text");
+ assertThat(wasEnqueued).isTrue();
verifyToastShownForTestPackage("Text", DEFAULT_DISPLAY);
}
@@ -8367,10 +8375,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.thenReturn(false);
// enqueue toast -> no toasts enqueued
- enqueueTextToast(testPackage, "Text");
+ boolean wasEnqueued = enqueueTextToast(testPackage, "Text");
verify(mStatusBar, never()).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any(),
anyInt());
assertEquals(0, mService.mToastQueue.size());
+ assertThat(wasEnqueued).isFalse();
}
@Test
@@ -8390,10 +8399,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
// enqueue toast -> no toasts enqueued
- enqueueToast(testPackage, new TestableToastCallback());
+ boolean wasEnqueued = enqueueToast(testPackage, new TestableToastCallback());
verify(mStatusBar, never()).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any(),
anyInt());
assertEquals(0, mService.mToastQueue.size());
+ assertThat(wasEnqueued).isFalse();
}
@Test
@@ -8415,8 +8425,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setAppInForegroundForToasts(mUid, false);
// enqueue toast -> no toasts enqueued
- enqueueToast(testPackage, new TestableToastCallback());
+ boolean wasEnqueued = enqueueToast(testPackage, new TestableToastCallback());
assertEquals(0, mService.mToastQueue.size());
+ assertThat(wasEnqueued).isFalse();
}
@Test
@@ -8437,8 +8448,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setAppInForegroundForToasts(mUid, false);
// enqueue toast -> system toast can still be enqueued
- enqueueToast(testPackage, new TestableToastCallback());
+ boolean wasEnqueued = enqueueToast(testPackage, new TestableToastCallback());
assertEquals(1, mService.mToastQueue.size());
+ assertThat(wasEnqueued).isTrue();
}
@Test
@@ -8458,7 +8470,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Trying to quickly enqueue more toast than allowed.
for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS + 1; i++) {
- enqueueTextToast(testPackage, "Text");
+ boolean wasEnqueued = enqueueTextToast(testPackage, "Text");
+ if (i < NotificationManagerService.MAX_PACKAGE_TOASTS) {
+ assertThat(wasEnqueued).isTrue();
+ } else {
+ assertThat(wasEnqueued).isFalse();
+ }
}
// Only allowed number enqueued, rest ignored.
assertEquals(NotificationManagerService.MAX_PACKAGE_TOASTS, mService.mToastQueue.size());
@@ -15089,25 +15106,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.thenReturn(false);
}
- private void enqueueToast(String testPackage, ITransientNotification callback)
+ private boolean enqueueToast(String testPackage, ITransientNotification callback)
throws RemoteException {
- enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), callback);
+ return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(),
+ callback);
}
- private void enqueueToast(INotificationManager service, String testPackage,
+ private boolean enqueueToast(INotificationManager service, String testPackage,
IBinder token, ITransientNotification callback) throws RemoteException {
- service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */ true,
- DEFAULT_DISPLAY);
+ return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */
+ true, DEFAULT_DISPLAY);
}
- private void enqueueTextToast(String testPackage, CharSequence text) throws RemoteException {
- enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY);
+ private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException {
+ return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY);
}
- private void enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext,
+ private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext,
int displayId) throws RemoteException {
- ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(), text,
- TOAST_DURATION, isUiContext, displayId, /* textCallback= */ null);
+ return ((INotificationManager) mService.mService).enqueueTextToast(testPackage,
+ new Binder(), text, TOAST_DURATION, isUiContext, displayId,
+ /* textCallback= */ null);
}
private void mockIsVisibleBackgroundUsersSupported(boolean supported) {
diff --git a/services/tests/vibrator/AndroidManifest.xml b/services/tests/vibrator/AndroidManifest.xml
index a14ea5598758..c0f514fb9673 100644
--- a/services/tests/vibrator/AndroidManifest.xml
+++ b/services/tests/vibrator/AndroidManifest.xml
@@ -30,6 +30,8 @@
<uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE" />
<!-- Required to set always-on vibrations -->
<uses-permission android:name="android.permission.VIBRATE_ALWAYS_ON" />
+ <!-- Required to play system-only haptic feedback constants -->
+ <uses-permission android:name="android.permission.VIBRATE_SYSTEM_CONSTANTS" />
<application android:debuggable="true">
<uses-library android:name="android.test.mock" android:required="true" />
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index e3d45967848a..633a3c985b7f 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -27,6 +27,8 @@ import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
import static android.os.VibrationEffect.EFFECT_CLICK;
import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK;
import static android.os.VibrationEffect.EFFECT_TICK;
+import static android.view.HapticFeedbackConstants.BIOMETRIC_CONFIRM;
+import static android.view.HapticFeedbackConstants.BIOMETRIC_REJECT;
import static android.view.HapticFeedbackConstants.CLOCK_TICK;
import static android.view.HapticFeedbackConstants.CONTEXT_CLICK;
import static android.view.HapticFeedbackConstants.KEYBOARD_RELEASE;
@@ -80,6 +82,8 @@ public class HapticFeedbackVibrationProviderTest {
new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK};
private static final int[] KEYBOARD_FEEDBACK_CONSTANTS =
new int[] {KEYBOARD_TAP, KEYBOARD_RELEASE};
+ private static final int[] BIOMETRIC_FEEDBACK_CONSTANTS =
+ new int[] {BIOMETRIC_CONFIRM, BIOMETRIC_REJECT};
private static final float KEYBOARD_VIBRATION_FIXED_AMPLITUDE = 0.62f;
@@ -283,6 +287,17 @@ public class HapticFeedbackVibrationProviderTest {
}
@Test
+ public void testVibrationAttribute_biometricConstants_returnsCommunicationRequestUsage() {
+ HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+ for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) {
+ VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ effectId, /* bypassVibrationIntensitySetting= */ false, /* fromIme= */ false);
+ assertThat(attrs.getUsage()).isEqualTo(VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
+ }
+ }
+
+ @Test
public void testVibrationAttribute_forNotBypassingIntensitySettings() {
HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
@@ -422,6 +437,15 @@ public class HapticFeedbackVibrationProviderTest {
}
}
+ @Test
+ public void testIsRestricted_biometricConstants_returnsTrue() {
+ HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+ for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) {
+ assertThat(hapticProvider.isRestrictedHapticFeedback(effectId)).isTrue();
+ }
+ }
+
private HapticFeedbackVibrationProvider createProviderWithDefaultCustomizations() {
return createProvider(/* customizations= */ null);
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 185677f966a4..d6c0fef9649a 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1410,6 +1410,70 @@ public class VibratorManagerServiceTest {
}
@Test
+ public void performHapticFeedback_restrictedConstantsWithoutPermission_doesNotVibrate()
+ throws Exception {
+ // Deny permission to vibrate with restricted constants
+ denyPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS);
+ // Public constant, no permission required
+ mHapticFeedbackVibrationMap.put(
+ HapticFeedbackConstants.CONFIRM,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+ // Hidden system-only constant, permission required
+ mHapticFeedbackVibrationMap.put(
+ HapticFeedbackConstants.BIOMETRIC_CONFIRM,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK));
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setSupportedEffects(
+ VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_HEAVY_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+
+ performHapticFeedbackAndWaitUntilFinished(
+ service, HapticFeedbackConstants.CONFIRM, /* always= */ false);
+
+ performHapticFeedbackAndWaitUntilFinished(
+ service, HapticFeedbackConstants.BIOMETRIC_CONFIRM, /* always= */ false);
+
+ List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
+ assertEquals(1, playedSegments.size());
+ PrebakedSegment segment = (PrebakedSegment) playedSegments.get(0);
+ assertEquals(VibrationEffect.EFFECT_CLICK, segment.getEffectId());
+ }
+
+ @Test
+ public void performHapticFeedback_restrictedConstantsWithPermission_playsVibration()
+ throws Exception {
+ // Grant permission to vibrate with restricted constants
+ grantPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS);
+ // Public constant, no permission required
+ mHapticFeedbackVibrationMap.put(
+ HapticFeedbackConstants.CONFIRM,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+ // Hidden system-only constant, permission required
+ mHapticFeedbackVibrationMap.put(
+ HapticFeedbackConstants.BIOMETRIC_CONFIRM,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK));
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setSupportedEffects(
+ VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_HEAVY_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+
+ performHapticFeedbackAndWaitUntilFinished(
+ service, HapticFeedbackConstants.CONFIRM, /* always= */ false);
+
+ performHapticFeedbackAndWaitUntilFinished(
+ service, HapticFeedbackConstants.BIOMETRIC_CONFIRM, /* always= */ false);
+
+ List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
+ assertEquals(2, playedSegments.size());
+ assertEquals(VibrationEffect.EFFECT_CLICK,
+ ((PrebakedSegment) playedSegments.get(0)).getEffectId());
+ assertEquals(VibrationEffect.EFFECT_HEAVY_CLICK,
+ ((PrebakedSegment) playedSegments.get(1)).getEffectId());
+ }
+
+ @Test
public void performHapticFeedback_doesNotVibrateWhenVibratorInfoNotReady() throws Exception {
denyPermission(android.Manifest.permission.VIBRATE);
mHapticFeedbackVibrationMap.put(
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 52df010bd588..eac99298559e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -85,7 +85,6 @@ import android.os.VibratorInfo;
import android.os.test.TestLooper;
import android.service.dreams.DreamManagerInternal;
import android.telecom.TelecomManager;
-import android.util.FeatureFlagUtils;
import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
@@ -743,15 +742,8 @@ class TestPhoneWindowManager {
void assertSwitchKeyboardLayout(int direction, int displayId) {
mTestLooper.dispatchAll();
- if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
- verify(mInputMethodManagerInternal).onSwitchKeyboardLayoutShortcut(eq(direction),
- eq(displayId), eq(mImeTargetWindowToken));
- verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt());
- } else {
- verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction));
- verify(mInputMethodManagerInternal, never())
- .onSwitchKeyboardLayoutShortcut(anyInt(), anyInt(), any());
- }
+ verify(mInputMethodManagerInternal).onSwitchKeyboardLayoutShortcut(eq(direction),
+ eq(displayId), eq(mImeTargetWindowToken));
}
void assertTakeBugreport() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 13550923cf3d..10eae577f706 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -46,6 +46,7 @@ import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
+import static android.server.wm.ActivityManagerTestBase.isTablet;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -75,6 +76,7 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -122,6 +124,7 @@ import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
import com.android.server.wm.utils.MockTracker;
+import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.Before;
@@ -1295,6 +1298,12 @@ public class ActivityStarterTests extends WindowTestsBase {
*/
@Test
public void testDeliverIntentToTopActivityOfNonTopDisplay() {
+ // TODO(b/330152508): Remove check once legacy multi-display behaviour can coexist with
+ // desktop windowing mode
+ // Ignore test if desktop windowing is enabled on tablets as legacy multi-display
+ // behaviour will not be respected
+ assumeFalse(Flags.enableDesktopWindowingMode() && isTablet());
+
final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
false /* mockGetRootTask */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index 5aa4ba3e1d42..695faa525dd6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -504,22 +504,37 @@ public class BackgroundActivityStartControllerTests {
assertThat(balState.callerExplicitOptInOrOut()).isFalse();
assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isTrue();
assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
- assertThat(balState.toString()).isEqualTo(
- "[callingPackage: package.app1; callingPackageTargetSdk: -1; callingUid: 10001; "
- + "callingPid: 11001; appSwitchState: 0; callingUidHasAnyVisibleWindow: "
- + "false; callingUidProcState: NONEXISTENT; "
- + "isCallingUidPersistentSystemProcess: false; forcedBalByPiSender: BSP"
- + ".NONE; intent: Intent { cmp=package.app3/someClass }; callerApp: "
- + "mCallerApp; inVisibleTask: false; balAllowedByPiCreator: BSP"
- + ".ALLOW_BAL; balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
- + "resultIfPiCreatorAllowsBal: null; hasRealCaller: true; "
- + "isCallForResult: false; isPendingIntent: false; autoOptInReason: "
- + "notPendingIntent; realCallingPackage: uid=1[debugOnly]; "
- + "realCallingPackageTargetSdk: -1; realCallingUid: 1; realCallingPid: 1;"
- + " realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: "
- + "NONEXISTENT; isRealCallingUidPersistentSystemProcess: false; "
- + "originatingPendingIntent: null; realCallerApp: null; "
- + "balAllowedByPiSender: BSP.ALLOW_BAL; resultIfPiSenderAllowsBal: null]");
+ assertThat(balState.toString()).contains(
+ "[callingPackage: package.app1; "
+ + "callingPackageTargetSdk: -1; "
+ + "callingUid: 10001; "
+ + "callingPid: 11001; "
+ + "appSwitchState: 0; "
+ + "callingUidHasAnyVisibleWindow: false; "
+ + "callingUidProcState: NONEXISTENT; "
+ + "isCallingUidPersistentSystemProcess: false; "
+ + "forcedBalByPiSender: BSP.NONE; "
+ + "intent: Intent { cmp=package.app3/someClass }; "
+ + "callerApp: mCallerApp; "
+ + "inVisibleTask: false; "
+ + "balAllowedByPiCreator: BSP.ALLOW_BAL; "
+ + "balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
+ + "resultIfPiCreatorAllowsBal: null; "
+ + "hasRealCaller: true; "
+ + "isCallForResult: false; "
+ + "isPendingIntent: false; "
+ + "autoOptInReason: notPendingIntent; "
+ + "realCallingPackage: uid=1[debugOnly]; "
+ + "realCallingPackageTargetSdk: -1; "
+ + "realCallingUid: 1; "
+ + "realCallingPid: 1; "
+ + "realCallingUidHasAnyVisibleWindow: false; "
+ + "realCallingUidProcState: NONEXISTENT; "
+ + "isRealCallingUidPersistentSystemProcess: false; "
+ + "originatingPendingIntent: null; "
+ + "realCallerApp: null; "
+ + "balAllowedByPiSender: BSP.ALLOW_BAL; "
+ + "resultIfPiSenderAllowsBal: null");
}
@Test
@@ -588,21 +603,36 @@ public class BackgroundActivityStartControllerTests {
assertThat(balState.callerExplicitOptInOrOut()).isFalse();
assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isFalse();
assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
- assertThat(balState.toString()).isEqualTo(
- "[callingPackage: package.app1; callingPackageTargetSdk: -1; callingUid: 10001; "
- + "callingPid: 11001; appSwitchState: 0; callingUidHasAnyVisibleWindow: "
- + "false; callingUidProcState: NONEXISTENT; "
- + "isCallingUidPersistentSystemProcess: false; forcedBalByPiSender: BSP"
- + ".NONE; intent: Intent { cmp=package.app3/someClass }; callerApp: "
- + "mCallerApp; inVisibleTask: false; balAllowedByPiCreator: BSP"
- + ".NONE; balAllowedByPiCreatorWithHardening: BSP.NONE; "
- + "resultIfPiCreatorAllowsBal: null; hasRealCaller: true; "
- + "isCallForResult: false; isPendingIntent: true; autoOptInReason: "
- + "null; realCallingPackage: uid=1[debugOnly]; "
- + "realCallingPackageTargetSdk: -1; realCallingUid: 1; realCallingPid: 1;"
- + " realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: "
- + "NONEXISTENT; isRealCallingUidPersistentSystemProcess: false; "
- + "originatingPendingIntent: PendingIntentRecord; realCallerApp: null; "
- + "balAllowedByPiSender: BSP.ALLOW_FGS; resultIfPiSenderAllowsBal: null]");
+ assertThat(balState.toString()).contains(
+ "[callingPackage: package.app1; "
+ + "callingPackageTargetSdk: -1; "
+ + "callingUid: 10001; "
+ + "callingPid: 11001; "
+ + "appSwitchState: 0; "
+ + "callingUidHasAnyVisibleWindow: false; "
+ + "callingUidProcState: NONEXISTENT; "
+ + "isCallingUidPersistentSystemProcess: false; "
+ + "forcedBalByPiSender: BSP.NONE; "
+ + "intent: Intent { cmp=package.app3/someClass }; "
+ + "callerApp: mCallerApp; "
+ + "inVisibleTask: false; "
+ + "balAllowedByPiCreator: BSP.NONE; "
+ + "balAllowedByPiCreatorWithHardening: BSP.NONE; "
+ + "resultIfPiCreatorAllowsBal: null; "
+ + "hasRealCaller: true; "
+ + "isCallForResult: false; "
+ + "isPendingIntent: true; "
+ + "autoOptInReason: null; "
+ + "realCallingPackage: uid=1[debugOnly]; "
+ + "realCallingPackageTargetSdk: -1; "
+ + "realCallingUid: 1; "
+ + "realCallingPid: 1; "
+ + "realCallingUidHasAnyVisibleWindow: false; "
+ + "realCallingUidProcState: NONEXISTENT; "
+ + "isRealCallingUidPersistentSystemProcess: false; "
+ + "originatingPendingIntent: PendingIntentRecord; "
+ + "realCallerApp: null; "
+ + "balAllowedByPiSender: BSP.ALLOW_FGS; "
+ + "resultIfPiSenderAllowsBal: null");
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 856ad2a02444..fbf142632c78 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -130,6 +130,7 @@ import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -2363,6 +2364,92 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ public void testUserOverrideFullscreenForLandscapeDisplay() {
+ final int displayWidth = 1600;
+ final int displayHeight = 1400;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ spyOn(mActivity.mWmService.mLetterboxConfiguration);
+ doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration)
+ .isUserAppAspectRatioFullscreenEnabled();
+
+ // Set user aspect ratio override
+ spyOn(mActivity.mLetterboxUiController);
+ doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mActivity.mLetterboxUiController)
+ .getUserMinAspectRatioOverrideCode();
+
+ prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
+
+ final Rect bounds = mActivity.getBounds();
+
+ // bounds should be fullscreen
+ assertEquals(displayHeight, bounds.height());
+ assertEquals(displayWidth, bounds.width());
+ }
+
+ @Test
+ public void testUserOverrideFullscreenForPortraitDisplay() {
+ final int displayWidth = 1400;
+ final int displayHeight = 1600;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ spyOn(mActivity.mWmService.mLetterboxConfiguration);
+ doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration)
+ .isUserAppAspectRatioFullscreenEnabled();
+
+ // Set user aspect ratio override
+ spyOn(mActivity.mLetterboxUiController);
+ doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mActivity.mLetterboxUiController)
+ .getUserMinAspectRatioOverrideCode();
+
+ prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);
+
+ final Rect bounds = mActivity.getBounds();
+
+ // bounds should be fullscreen
+ assertEquals(displayHeight, bounds.height());
+ assertEquals(displayWidth, bounds.width());
+ }
+
+ @Test
+ public void testSystemFullscreenOverrideForLandscapeDisplay() {
+ final int displayWidth = 1600;
+ final int displayHeight = 1400;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ spyOn(mActivity.mLetterboxUiController);
+ doReturn(true).when(mActivity.mLetterboxUiController)
+ .isSystemOverrideToFullscreenEnabled();
+
+ prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
+
+ final Rect bounds = mActivity.getBounds();
+
+ // bounds should be fullscreen
+ assertEquals(displayHeight, bounds.height());
+ assertEquals(displayWidth, bounds.width());
+ }
+
+ @Test
+ public void testSystemFullscreenOverrideForPortraitDisplay() {
+ final int displayWidth = 1400;
+ final int displayHeight = 1600;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ spyOn(mActivity.mLetterboxUiController);
+ doReturn(true).when(mActivity.mLetterboxUiController)
+ .isSystemOverrideToFullscreenEnabled();
+
+ prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);
+
+ final Rect bounds = mActivity.getBounds();
+
+ // bounds should be fullscreen
+ assertEquals(displayHeight, bounds.height());
+ assertEquals(displayWidth, bounds.width());
+ }
+
+ @Test
public void testUserOverrideSplitScreenAspectRatioForLandscapeDisplay() {
final int displayWidth = 1600;
final int displayHeight = 1400;
@@ -4117,6 +4204,7 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ @Ignore // TODO(b/330888878): fix test in main
public void testPortraitCloseToSquareDisplayWithTaskbar_notLetterboxed() {
if (Flags.insetsDecoupledConfiguration()) {
// TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 018600641853..42fe3a747b64 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1630,6 +1630,7 @@ public class TransitionTests extends WindowTestsBase {
assertTrue(controller.mWaitingTransitions.contains(transition));
assertTrue(controller.isTransientHide(appTask));
assertTrue(controller.isTransientVisible(appTask));
+ assertTrue(controller.isTransientLaunch(recent));
}
@Test
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index ae4faa84421e..9729c688439e 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -180,7 +180,8 @@ public class VoiceInteractionManagerService extends SystemService {
LocalServices.getService(ActivityManagerInternal.class));
mAtmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityTaskManagerInternal.class));
- mWmInternal = LocalServices.getService(WindowManagerInternal.class);
+ mWmInternal = Objects.requireNonNull(
+ LocalServices.getService(WindowManagerInternal.class));
mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
LegacyPermissionManagerInternal permissionManagerInternal = LocalServices.getService(
LegacyPermissionManagerInternal.class);
@@ -2737,12 +2738,8 @@ public class VoiceInteractionManagerService extends SystemService {
isManagedProfileVisible = true;
}
}
- final ScreenCapture.ScreenshotHardwareBuffer shb;
- if (mWmInternal != null) {
- shb = mWmInternal.takeAssistScreenshot();
- } else {
- shb = null;
- }
+ final ScreenCapture.ScreenshotHardwareBuffer shb =
+ mWmInternal.takeAssistScreenshot();
final Bitmap bm = shb != null ? shb.asBitmap() : null;
// Now that everything is fetched, putting it in the launchIntent.
if (bm != null) {
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index ec60c676d078..0ddc38a78b1e 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -35,8 +35,6 @@ import android.telephony.TelephonyManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.flags.FeatureFlags;
-import com.android.internal.telephony.flags.FeatureFlagsImpl;
import java.util.HashMap;
import java.util.HashSet;
@@ -48,8 +46,7 @@ public final class TelephonyPermissions {
private static final String LOG_TAG = "TelephonyPermissions";
private static final boolean DBG = false;
- /** Feature flags */
- private static final FeatureFlags sFeatureFlag = new FeatureFlagsImpl();
+
/**
* Whether to disable the new device identifier access restrictions.
*/
@@ -886,12 +883,6 @@ public final class TelephonyPermissions {
*/
public static boolean checkSubscriptionAssociatedWithUser(@NonNull Context context, int subId,
@NonNull UserHandle callerUserHandle) {
- if (!sFeatureFlag.rejectBadSubIdInteraction()
- && !SubscriptionManager.isValidSubscriptionId(subId)) {
- // Return true for invalid sub Id.
- return true;
- }
-
SubscriptionManager subManager = (SubscriptionManager) context.getSystemService(
Context.TELEPHONY_SUBSCRIPTION_SERVICE);
final long token = Binder.clearCallingIdentity();
@@ -906,7 +897,7 @@ public final class TelephonyPermissions {
} catch (IllegalArgumentException e) {
// Found no record of this sub Id.
Log.e(LOG_TAG, "Subscription[Subscription ID:" + subId + "] has no records on device");
- return !sFeatureFlag.rejectBadSubIdInteraction();
+ return false;
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 10c17c1be6e6..7db41804067b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9898,6 +9898,50 @@ public class CarrierConfigManager {
*/
public static final String KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING =
"satellite_information_redirect_url_string";
+ /**
+ * Indicate whether a carrier supports emergency messaging. When this config is {@code false},
+ * emergency call to satellite T911 handover will be disabled.
+ *
+ * This will need agreement with carriers before enabling this flag.
+ *
+ * The default value is false.
+ *
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL =
+ "emergency_messaging_supported_bool";
+
+ /**
+ * An integer key holds the timeout duration in milliseconds used to determine whether to hand
+ * over an emergency call to satellite T911.
+ *
+ * The timer is started when there is an ongoing emergency call, and the IMS is not registered,
+ * and cellular service is not available. When the timer expires,
+ * {@link com.android.internal.telephony.satellite.SatelliteSOSMessageRecommender} will send the
+ * event {@link TelephonyManager#EVENT_DISPLAY_EMERGENCY_MESSAGE} to Dialer, which will then
+ * prompt user to switch to using satellite emergency messaging.
+ *
+ * The default value is 30 seconds.
+ *
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT =
+ "emergency_call_to_satellite_t911_handover_timeout_millis_int";
+
+ /**
+ * An int array that contains default capabilities for carrier enabled satellite roaming.
+ * If any PLMN is provided from the entitlement server, and it is not listed in
+ * {@link #KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE}, default capabilities
+ * will be used instead.
+ * <p>
+ * The default capabilities are
+ * {@link NetworkRegistrationInfo#SERVICE_TYPE_SMS}, and
+ * {@link NetworkRegistrationInfo#SERVICE_TYPE_MMS}
+ *
+ * @hide
+ */
+ public static final String KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY =
+ "carrier_roaming_satellite_default_services_int_array";
/**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
@@ -11045,7 +11089,15 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
sDefaults.putString(KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING, "androidSatmode");
sDefaults.putString(KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING, "");
+ sDefaults.putIntArray(KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY,
+ new int[] {
+ NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+ NetworkRegistrationInfo.SERVICE_TYPE_MMS
+ });
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
+ sDefaults.putBoolean(KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL, false);
+ sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
+ (int) TimeUnit.SECONDS.toMillis(30));
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index c5f2d42389e5..ba7ba53272f1 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3137,7 +3137,7 @@ public class SubscriptionManager {
if (useRootLocale) {
configurationKey.setLocale(Locale.ROOT);
}
- cacheKey = Pair.create(context.getPackageName(), configurationKey);
+ cacheKey = Pair.create(context.getPackageName() + ", subid=" + subId, configurationKey);
synchronized (sResourcesCache) {
Resources cached = sResourcesCache.get(cacheKey);
if (cached != null) {
diff --git a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
index 06fc3c631eba..579fda320e9a 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
@@ -26,11 +26,13 @@ oneway interface ISatelliteTransmissionUpdateCallback {
/**
* Called when satellite datagram send state changed.
*
+ * @param datagramType The datagram type of currently being sent.
* @param state The new send datagram transfer state.
* @param sendPendingCount The number of datagrams that are currently being sent.
* @param errorCode If datagram transfer failed, the reason for failure.
*/
- void onSendDatagramStateChanged(in int state, in int sendPendingCount, in int errorCode);
+ void onSendDatagramStateChanged(int datagramType, int state, int sendPendingCount,
+ int errorCode);
/**
* Called when satellite datagram receive state changed.
@@ -39,7 +41,7 @@ oneway interface ISatelliteTransmissionUpdateCallback {
* @param receivePendingCount The number of datagrams that are currently pending to be received.
* @param errorCode If datagram transfer failed, the reason for failure.
*/
- void onReceiveDatagramStateChanged(in int state, in int receivePendingCount, in int errorCode);
+ void onReceiveDatagramStateChanged(int state, int receivePendingCount, int errorCode);
/**
* Called when the satellite position changed.
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 20b24b9db6f4..87bb0f05c742 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -992,12 +992,19 @@ public final class SatelliteManager {
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2;
+ /**
+ * This type of datagram is used to keep the device in satellite connected state or check if
+ * there is any incoming message.
+ * @hide
+ */
+ public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3;
/** @hide */
@IntDef(prefix = "DATAGRAM_TYPE_", value = {
DATAGRAM_TYPE_UNKNOWN,
DATAGRAM_TYPE_SOS_MESSAGE,
- DATAGRAM_TYPE_LOCATION_SHARING
+ DATAGRAM_TYPE_LOCATION_SHARING,
+ DATAGRAM_TYPE_KEEP_ALIVE
})
@Retention(RetentionPolicy.SOURCE)
public @interface DatagramType {}
@@ -1077,8 +1084,13 @@ public final class SatelliteManager {
}
@Override
- public void onSendDatagramStateChanged(int state, int sendPendingCount,
- int errorCode) {
+ public void onSendDatagramStateChanged(int datagramType, int state,
+ int sendPendingCount, int errorCode) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSendDatagramStateChanged(datagramType,
+ state, sendPendingCount, errorCode)));
+
+ // For backward compatibility
executor.execute(() -> Binder.withCleanCallingIdentity(
() -> callback.onSendDatagramStateChanged(
state, sendPendingCount, errorCode)));
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index e02097019c4c..d8bd66284fb0 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -52,6 +52,19 @@ public interface SatelliteTransmissionUpdateCallback {
@SatelliteManager.SatelliteResult int errorCode);
/**
+ * Called when satellite datagram send state changed.
+ *
+ * @param datagramType The datagram type of currently being sent.
+ * @param state The new send datagram transfer state.
+ * @param sendPendingCount The number of datagrams that are currently being sent.
+ * @param errorCode If datagram transfer failed, the reason for failure.
+ *
+ * @hide
+ */
+ void onSendDatagramStateChanged(@SatelliteManager.DatagramType int datagramType,
+ @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
+ @SatelliteManager.SatelliteResult int errorCode);
+ /**
* Called when satellite datagram receive state changed.
*
* @param state The new receive datagram transfer state.
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/Android.bp b/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
index 1e68c9dd459f..9994826d061a 100644
--- a/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
+++ b/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
@@ -19,10 +19,11 @@ package {
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_framework_cdm",
}
android_test {
- name: "cdm_snippet",
+ name: "cdm_snippet_legacy",
srcs: ["src/**/*.kt"],
manifest: "AndroidManifest.xml",
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
index 03335c7cd576..37cb8500fbab 100644
--- a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
+++ b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
@@ -19,6 +19,7 @@ package {
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_framework_cdm",
}
python_test_host {
@@ -36,7 +37,7 @@ python_test_host {
tags: ["mobly"],
},
data: [
- ":cdm_snippet",
+ ":cdm_snippet_legacy",
],
version: {
py2: {
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml b/tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml
index 9d1813ff79bc..7c7ef6345a41 100644
--- a/tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml
+++ b/tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml
@@ -24,12 +24,12 @@
<device name="device1">
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="test-file-name" value="cdm_snippet.apk" />
+ <option name="test-file-name" value="cdm_snippet_legacy.apk" />
</target_preparer>
</device>
<device name="device2">
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="test-file-name" value="cdm_snippet.apk" />
+ <option name="test-file-name" value="cdm_snippet_legacy.apk" />
</target_preparer>
</device>
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
index 46ad77e1eff9..519b4296d93a 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
@@ -16,8 +16,8 @@
package com.android.server.wm.flicker.activityembedding.close
+import android.graphics.Rect
import android.platform.test.annotations.Presubmit
-import android.tools.datatypes.Rect
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -122,7 +122,7 @@ class CloseSecondaryActivityInSplitTest(flicker: LegacyFlickerTest) :
companion object {
/** {@inheritDoc} */
- private var startDisplayBounds = Rect.EMPTY
+ private var startDisplayBounds = Rect()
/**
* Creates the test configurations.
*
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
index af4f7a721464..4cd6d15b2983 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
@@ -16,8 +16,8 @@
package com.android.server.wm.flicker.activityembedding.layoutchange
+import android.graphics.Rect
import android.platform.test.annotations.Presubmit
-import android.tools.datatypes.Rect
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -114,11 +114,11 @@ class HorizontalSplitChangeRatioTest(flicker: LegacyFlickerTest) :
// Compare dimensions of two splits, given we're using default split attributes,
// both activities take up the same visible size on the display.
check { "height" }
- .that(topLayerRegion.region.height)
- .isEqual(bottomLayerRegion.region.height)
+ .that(topLayerRegion.region.bounds.height())
+ .isEqual(bottomLayerRegion.region.bounds.height())
check { "width" }
- .that(topLayerRegion.region.width)
- .isEqual(bottomLayerRegion.region.width)
+ .that(topLayerRegion.region.bounds.width())
+ .isEqual(bottomLayerRegion.region.bounds.width())
topLayerRegion.notOverlaps(bottomLayerRegion.region)
// Layers of two activities sum to be fullscreen size on display.
topLayerRegion.plus(bottomLayerRegion.region).coversExactly(startDisplayBounds)
@@ -132,14 +132,17 @@ class HorizontalSplitChangeRatioTest(flicker: LegacyFlickerTest) :
// Compare dimensions of two splits, given we're using default split attributes,
// both activities take up the same visible size on the display.
check { "height" }
- .that(topLayerRegion.region.height)
- .isLower(bottomLayerRegion.region.height)
+ .that(topLayerRegion.region.bounds.height())
+ .isLower(bottomLayerRegion.region.bounds.height())
check { "height" }
- .that(topLayerRegion.region.height / 0.3f - bottomLayerRegion.region.height / 0.7f)
+ .that(
+ topLayerRegion.region.bounds.height() / 0.3f -
+ bottomLayerRegion.region.bounds.height() / 0.7f
+ )
.isLower(0.1f)
check { "width" }
- .that(topLayerRegion.region.width)
- .isEqual(bottomLayerRegion.region.width)
+ .that(topLayerRegion.region.bounds.width())
+ .isEqual(bottomLayerRegion.region.bounds.width())
topLayerRegion.notOverlaps(bottomLayerRegion.region)
// Layers of two activities sum to be fullscreen size on display.
topLayerRegion.plus(bottomLayerRegion.region).coversExactly(startDisplayBounds)
@@ -148,7 +151,7 @@ class HorizontalSplitChangeRatioTest(flicker: LegacyFlickerTest) :
companion object {
/** {@inheritDoc} */
- private var startDisplayBounds = Rect.EMPTY
+ private var startDisplayBounds = Rect()
/**
* Creates the test configurations.
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
index e511b727d57f..5df8b57294f0 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -16,8 +16,8 @@
package com.android.server.wm.flicker.activityembedding.open
+import android.graphics.Rect
import android.platform.test.annotations.Presubmit
-import android.tools.datatypes.Rect
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -132,7 +132,7 @@ class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: LegacyFlickerTest
companion object {
/** {@inheritDoc} */
- private var startDisplayBounds = Rect.EMPTY
+ private var startDisplayBounds = Rect()
/**
* Creates the test configurations.
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
index 4352177a8984..78004ccc3f97 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
@@ -16,8 +16,8 @@
package com.android.server.wm.flicker.activityembedding.open
+import android.graphics.Rect
import android.platform.test.annotations.Presubmit
-import android.tools.datatypes.Rect
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -143,7 +143,7 @@ class OpenThirdActivityOverSplitTest(flicker: LegacyFlickerTest) :
companion object {
/** {@inheritDoc} */
- private var startDisplayBounds = Rect.EMPTY
+ private var startDisplayBounds = Rect()
/**
* Creates the test configurations.
*
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
index 62cf6cd528e9..cf4edd50040b 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -16,8 +16,8 @@
package com.android.server.wm.flicker.activityembedding.open
+import android.graphics.Rect
import android.platform.test.annotations.Presubmit
-import android.tools.datatypes.Rect
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -156,11 +156,11 @@ class OpenTrampolineActivityTest(flicker: LegacyFlickerTest) : ActivityEmbedding
it.timestamp
)
check { "height" }
- .that(mainActivityRegion.region.height)
- .isEqual(secondaryActivityRegion.region.height)
+ .that(mainActivityRegion.region.bounds.height())
+ .isEqual(secondaryActivityRegion.region.bounds.height())
check { "width" }
- .that(mainActivityRegion.region.width)
- .isEqual(secondaryActivityRegion.region.width)
+ .that(mainActivityRegion.region.bounds.width())
+ .isEqual(secondaryActivityRegion.region.bounds.width())
mainActivityRegion
.plus(secondaryActivityRegion.region)
.coversExactly(startDisplayBounds)
@@ -192,11 +192,11 @@ class OpenTrampolineActivityTest(flicker: LegacyFlickerTest) : ActivityEmbedding
// Compare dimensions of two splits, given we're using default split attributes,
// both activities take up the same visible size on the display.
check { "height" }
- .that(leftLayerRegion.region.height)
- .isEqual(rightLayerRegion.region.height)
+ .that(leftLayerRegion.region.bounds.height())
+ .isEqual(rightLayerRegion.region.bounds.height())
check { "width" }
- .that(leftLayerRegion.region.width)
- .isEqual(rightLayerRegion.region.width)
+ .that(leftLayerRegion.region.bounds.width())
+ .isEqual(rightLayerRegion.region.bounds.width())
leftLayerRegion.notOverlaps(rightLayerRegion.region)
// Layers of two activities sum to be fullscreen size on display.
leftLayerRegion.plus(rightLayerRegion.region).coversExactly(startDisplayBounds)
@@ -211,7 +211,7 @@ class OpenTrampolineActivityTest(flicker: LegacyFlickerTest) : ActivityEmbedding
companion object {
/** {@inheritDoc} */
- private var startDisplayBounds = Rect.EMPTY
+ private var startDisplayBounds = Rect()
/**
* Creates the test configurations.
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index aa8b4cebe91d..bc3696b3ed1c 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -16,8 +16,8 @@
package com.android.server.wm.flicker.activityembedding.pip
+import android.graphics.Rect
import android.platform.test.annotations.Presubmit
-import android.tools.datatypes.Rect
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -79,11 +79,11 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :
// Compare dimensions of two splits, given we're using default split attributes,
// both activities take up the same visible size on the display.
check { "height" }
- .that(leftLayerRegion.region.height)
- .isEqual(rightLayerRegion.region.height)
+ .that(leftLayerRegion.region.bounds.height())
+ .isEqual(rightLayerRegion.region.bounds.height())
check { "width" }
- .that(leftLayerRegion.region.width)
- .isEqual(rightLayerRegion.region.width)
+ .that(leftLayerRegion.region.bounds.width())
+ .isEqual(rightLayerRegion.region.bounds.width())
leftLayerRegion.notOverlaps(rightLayerRegion.region)
leftLayerRegion.plus(rightLayerRegion.region).coversExactly(startDisplayBounds)
}
@@ -136,9 +136,11 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :
val pipWindowRegion =
visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
check { "height" }
- .that(pipWindowRegion.region.height)
- .isLower(startDisplayBounds.height / 2)
- check { "width" }.that(pipWindowRegion.region.width).isLower(startDisplayBounds.width)
+ .that(pipWindowRegion.region.bounds.height())
+ .isLower(startDisplayBounds.height() / 2)
+ check { "width" }
+ .that(pipWindowRegion.region.bounds.width())
+ .isLower(startDisplayBounds.width())
}
}
@@ -151,7 +153,7 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :
ComponentNameMatcher.PIP_CONTENT_OVERLAY.layerMatchesAnyOf(it) && it.isVisible
}
pipLayerList.zipWithNext { previous, current ->
- if (startDisplayBounds.width > startDisplayBounds.height) {
+ if (startDisplayBounds.width() > startDisplayBounds.height()) {
// Only verify when the display is landscape, because otherwise the final pip
// window can be to the left of the original secondary activity.
current.screenBounds.isToTheRightBottom(previous.screenBounds.region, 3)
@@ -162,8 +164,12 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :
}
flicker.assertLayersEnd {
val pipRegion = visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
- check { "height" }.that(pipRegion.region.height).isLower(startDisplayBounds.height / 2)
- check { "width" }.that(pipRegion.region.width).isLower(startDisplayBounds.width)
+ check { "height" }
+ .that(pipRegion.region.bounds.height())
+ .isLower(startDisplayBounds.height() / 2)
+ check { "width" }
+ .that(pipRegion.region.bounds.width())
+ .isLower(startDisplayBounds.width())
}
}
@@ -175,7 +181,7 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :
invoke("secondaryLayerNotJumpToLeft") {
val secondaryVisibleRegion =
it.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
- if (secondaryVisibleRegion.region.isNotEmpty) {
+ if (!secondaryVisibleRegion.region.isEmpty) {
check { "left" }.that(secondaryVisibleRegion.region.bounds.left).isGreater(0)
}
}
@@ -222,7 +228,7 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :
companion object {
/** {@inheritDoc} */
- private var startDisplayBounds = Rect.EMPTY
+ private var startDisplayBounds = Rect()
/**
* Creates the test configurations.
*
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
index 3d834c16163f..f5e6c7854eba 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
@@ -85,11 +85,11 @@ open class RotateSplitNoChangeTest(flicker: LegacyFlickerTest) : RotationTransit
// Compare dimensions of two splits, given we're using default split attributes,
// both activities take up the same visible size on the display.
check { "height" }
- .that(leftLayerRegion.region.height)
- .isEqual(rightLayerRegion.region.height)
+ .that(leftLayerRegion.region.bounds.height())
+ .isEqual(rightLayerRegion.region.bounds.height())
check { "width" }
- .that(leftLayerRegion.region.width)
- .isEqual(rightLayerRegion.region.width)
+ .that(leftLayerRegion.region.bounds.width())
+ .isEqual(rightLayerRegion.region.bounds.width())
leftLayerRegion.notOverlaps(rightLayerRegion.region)
// Layers of two activities sum to be fullscreen size on display.
leftLayerRegion.plus(rightLayerRegion.region).coversExactly(display.layerStackSpace)
@@ -108,11 +108,11 @@ open class RotateSplitNoChangeTest(flicker: LegacyFlickerTest) : RotationTransit
val rightLayerRegion =
this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
check { "height" }
- .that(leftLayerRegion.region.height)
- .isEqual(rightLayerRegion.region.height)
+ .that(leftLayerRegion.region.bounds.height())
+ .isEqual(rightLayerRegion.region.bounds.height())
check { "width" }
- .that(leftLayerRegion.region.width)
- .isEqual(rightLayerRegion.region.width)
+ .that(leftLayerRegion.region.bounds.width())
+ .isEqual(rightLayerRegion.region.bounds.width())
leftLayerRegion.notOverlaps(rightLayerRegion.region)
leftLayerRegion.plus(rightLayerRegion.region).coversExactly(display.layerStackSpace)
}
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
index 511c94849681..ee2c05e82d51 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
@@ -16,9 +16,13 @@
package com.android.server.wm.flicker.activityembedding.rotation
+import android.graphics.Rect
import android.platform.test.annotations.Presubmit
+import android.tools.Position
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.Condition
+import android.tools.traces.DeviceStateDump
import android.tools.traces.component.ComponentNameMatcher
import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.setRotation
@@ -30,7 +34,14 @@ abstract class RotationTransition(flicker: LegacyFlickerTest) : ActivityEmbeddin
override val transition: FlickerBuilder.() -> Unit = {
setup { this.setRotation(flicker.scenario.startRotation) }
teardown { testApp.exit(wmHelper) }
- transitions { this.setRotation(flicker.scenario.endRotation) }
+ transitions {
+ this.setRotation(flicker.scenario.endRotation)
+ if (!flicker.scenario.isTablet) {
+ wmHelper.StateSyncBuilder()
+ .add(navBarInPosition(flicker.scenario.isGesturalNavigation))
+ .waitForAndVerify()
+ }
+ }
}
/** {@inheritDoc} */
@@ -76,4 +87,37 @@ abstract class RotationTransition(flicker: LegacyFlickerTest) : ActivityEmbeddin
appLayerRotates_StartingPos()
appLayerRotates_EndingPos()
}
+
+ private fun navBarInPosition(isGesturalNavigation: Boolean): Condition<DeviceStateDump> {
+ return Condition("navBarPosition") { dump ->
+ val display =
+ dump.layerState.displays.filterNot { it.isOff }.minByOrNull { it.id }
+ ?: error("There is no display!")
+ val displayArea = display.layerStackSpace
+ val navBarPosition = display.navBarPosition(isGesturalNavigation)
+ val navBarRegion = dump.layerState
+ .getLayerWithBuffer(ComponentNameMatcher.NAV_BAR)
+ ?.visibleRegion?.bounds ?: Rect()
+
+ when (navBarPosition) {
+ Position.TOP ->
+ navBarRegion.top == displayArea.top &&
+ navBarRegion.left == displayArea.left &&
+ navBarRegion.right == displayArea.right
+ Position.BOTTOM ->
+ navBarRegion.bottom == displayArea.bottom &&
+ navBarRegion.left == displayArea.left &&
+ navBarRegion.right == displayArea.right
+ Position.LEFT ->
+ navBarRegion.left == displayArea.left &&
+ navBarRegion.top == displayArea.top &&
+ navBarRegion.bottom == displayArea.bottom
+ Position.RIGHT ->
+ navBarRegion.right == displayArea.right &&
+ navBarRegion.top == displayArea.top &&
+ navBarRegion.bottom == displayArea.bottom
+ else -> error("Unknown position $navBarPosition")
+ }
+ }
+ }
}
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index 7298e5f71b05..fb9258304870 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -16,9 +16,9 @@
package com.android.server.wm.flicker.activityembedding.splitscreen
+import android.graphics.Rect
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.tools.datatypes.Rect
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -32,6 +32,7 @@ import com.android.wm.shell.flicker.utils.SplitScreenUtils
import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
+import kotlin.math.abs
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
@@ -135,19 +136,25 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa
.plus(systemDivider.region)
.coversExactly(startDisplayBounds)
check { "ActivityEmbeddingSplitHeight" }
- .that(leftAELayerRegion.region.height)
- .isEqual(rightAELayerRegion.region.height)
+ .that(leftAELayerRegion.region.bounds.height())
+ .isEqual(rightAELayerRegion.region.bounds.height())
check { "SystemSplitHeight" }
- .that(rightAELayerRegion.region.height)
- .isEqual(secondaryAppLayerRegion.region.height)
+ .that(rightAELayerRegion.region.bounds.height())
+ .isEqual(secondaryAppLayerRegion.region.bounds.height())
// TODO(b/292283182): Remove this special case handling.
check { "ActivityEmbeddingSplitWidth" }
- .that(Math.abs(leftAELayerRegion.region.width - rightAELayerRegion.region.width))
+ .that(
+ abs(
+ leftAELayerRegion.region.bounds.width() -
+ rightAELayerRegion.region.bounds.width()
+ )
+ )
.isLower(2)
check { "SystemSplitWidth" }
.that(
- Math.abs(
- secondaryAppLayerRegion.region.width - 2 * rightAELayerRegion.region.width
+ abs(
+ secondaryAppLayerRegion.region.bounds.width() -
+ 2 * rightAELayerRegion.region.bounds.width()
)
)
.isLower(2)
@@ -167,18 +174,24 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa
val secondaryAppLayerRegion =
visibleRegion(ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent())
check { "ActivityEmbeddingSplitHeight" }
- .that(leftAEWindowRegion.region.height)
- .isEqual(rightAEWindowRegion.region.height)
+ .that(leftAEWindowRegion.region.bounds.height())
+ .isEqual(rightAEWindowRegion.region.bounds.height())
check { "SystemSplitHeight" }
- .that(rightAEWindowRegion.region.height)
- .isEqual(secondaryAppLayerRegion.region.height)
+ .that(rightAEWindowRegion.region.bounds.height())
+ .isEqual(secondaryAppLayerRegion.region.bounds.height())
check { "ActivityEmbeddingSplitWidth" }
- .that(Math.abs(leftAEWindowRegion.region.width - rightAEWindowRegion.region.width))
+ .that(
+ abs(
+ leftAEWindowRegion.region.bounds.width() -
+ rightAEWindowRegion.region.bounds.width()
+ )
+ )
.isLower(2)
check { "SystemSplitWidth" }
.that(
- Math.abs(
- secondaryAppLayerRegion.region.width - 2 * rightAEWindowRegion.region.width
+ abs(
+ secondaryAppLayerRegion.region.bounds.width() -
+ 2 * rightAEWindowRegion.region.bounds.width()
)
)
.isLower(2)
@@ -190,7 +203,7 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa
companion object {
/** {@inheritDoc} */
- private var startDisplayBounds = Rect.EMPTY
+ private var startDisplayBounds = Rect()
/**
* Creates the test configurations.
*
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index b1d78cbc034e..a71599d25632 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -19,8 +19,9 @@ package com.android.server.wm.flicker.launch
import android.app.Instrumentation
import android.app.WallpaperManager
import android.content.res.Resources
+import android.graphics.Rect
+import android.graphics.Region
import android.platform.test.annotations.Presubmit
-import android.tools.datatypes.Region
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -213,6 +214,12 @@ class TaskTransitionTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
private fun LayersTraceSubject.visibleRegionCovers(
component: IComponentMatcher,
+ expectedArea: Rect,
+ isOptional: Boolean = true
+ ): LayersTraceSubject = visibleRegionCovers(component, Region(expectedArea), isOptional)
+
+ private fun LayersTraceSubject.visibleRegionCovers(
+ component: IComponentMatcher,
expectedArea: Region,
isOptional: Boolean = true
): LayersTraceSubject =
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
index 7e486abbd30f..da8368f3cedf 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
@@ -83,11 +83,12 @@ class CloseImeOnDismissPopupDialogTest(flicker: LegacyFlickerTest) : BaseTest(fl
}
if (imeSnapshotLayers.isNotEmpty()) {
val visibleAreas =
- imeSnapshotLayers
- .mapNotNull { imeSnapshotLayer -> imeSnapshotLayer.layer.visibleRegion }
+ imeSnapshotLayers.mapNotNull { imeSnapshotLayer ->
+ imeSnapshotLayer.layer.visibleRegion
+ }
val imeVisibleRegion = RegionSubject(visibleAreas, timestamp)
val appVisibleRegion = it.visibleRegion(imeTestApp)
- if (imeVisibleRegion.region.isNotEmpty) {
+ if (!imeVisibleRegion.region.isEmpty) {
imeVisibleRegion.coversAtMost(appVisibleRegion.region)
}
}
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index e8249bca4c2d..48ec4d1fed2c 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -115,7 +115,10 @@ class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker: LegacyF
.isEqual(true)
imeLayerSubjects.forEach { imeLayerSubject ->
- imeLayerSubject.check { "alpha" }.that(imeLayerSubject.layer.color.a).isEqual(1.0f)
+ imeLayerSubject
+ .check { "alpha" }
+ .that(imeLayerSubject.layer.color.alpha())
+ .isEqual(1.0f)
}
}
}
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
index 617237d37368..92b865542257 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
@@ -50,7 +50,7 @@ class ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker: LegacyFlickerTe
testApp.launchViaIntent(wmHelper)
testApp.openIME(wmHelper)
this.setRotation(flicker.scenario.startRotation)
- device.pressRecentApps()
+ tapl.launchedAppState.switchToOverview()
wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify()
}
transitions {
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
index a14dc62b0023..7aa525fcccef 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
@@ -191,7 +191,7 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl
this.invoke("imeLayerIsVisibleAndAlignAppWidow") {
val imeVisibleRegion = it.visibleRegion(ComponentNameMatcher.IME)
val appVisibleRegion = it.visibleRegion(imeTestApp)
- if (imeVisibleRegion.region.isNotEmpty) {
+ if (!imeVisibleRegion.region.isEmpty) {
it.isVisible(ComponentNameMatcher.IME)
imeVisibleRegion.coversAtMost(appVisibleRegion.region)
}
diff --git a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 8b09b590e790..9bb62e1e1794 100644
--- a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -16,9 +16,9 @@
package com.android.server.wm.flicker.quickswitch
+import android.graphics.Rect
import android.platform.test.annotations.Presubmit
import android.tools.NavBar
-import android.tools.datatypes.Rect
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -237,7 +237,7 @@ class QuickSwitchBetweenTwoAppsBackTest(flicker: LegacyFlickerTest) : BaseTest(f
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
companion object {
- private var startDisplayBounds = Rect.EMPTY
+ private var startDisplayBounds = Rect()
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index c54ddcf793f6..491b9945d12d 100644
--- a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -16,8 +16,8 @@
package com.android.server.wm.flicker.quickswitch
+import android.graphics.Rect
import android.tools.NavBar
-import android.tools.datatypes.Rect
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -285,7 +285,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
- private var startDisplayBounds = Rect.EMPTY
+ private var startDisplayBounds = Rect()
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 69a84a0cbcb0..de54c95da361 100644
--- a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -16,10 +16,10 @@
package com.android.server.wm.flicker.quickswitch
+import android.graphics.Rect
import android.platform.test.annotations.Presubmit
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.datatypes.Rect
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -266,7 +266,7 @@ class QuickSwitchFromLauncherTest(flicker: LegacyFlickerTest) : BaseTest(flicker
companion object {
/** {@inheritDoc} */
- private var startDisplayBounds = Rect.EMPTY
+ private var startDisplayBounds = Rect()
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/README.md b/tests/FlickerTests/README.md
index 6b28fdf8a8ef..7429250f5cc0 100644
--- a/tests/FlickerTests/README.md
+++ b/tests/FlickerTests/README.md
@@ -7,82 +7,17 @@ The tests are organized in packages according to the transitions they test (e.g.
## Adding a Test
-By default tests should inherit from `RotationTestBase` or `NonRotationTestBase` and must override the variable `transitionToRun` (Kotlin) or the function `getTransitionToRun()` (Java).
-Only tests that are not supported by these classes should inherit directly from the `FlickerTestBase` class.
+By default, tests should inherit from `TestBase` and override the variable `transition` (Kotlin) or the function `getTransition()` (Java).
-### Rotation animations and transitions
+Inheriting from this class ensures the common assertions will be executed, namely:
-Tests that rotate the device should inherit from `RotationTestBase`.
-Tests that inherit from the class automatically receive start and end rotation values.
-Moreover, these tests inherit the following checks:
* all regions on the screen are covered
* status bar is always visible
-* status bar rotates
+* status bar is at the correct position at the start and end of the transition
* nav bar is always visible
-* nav bar is rotates
+* nav bar is at the correct position at the start and end of the transition
The default tests can be disabled by overriding the respective methods and including an `@Ignore` annotation.
-### Non-Rotation animations and transitions
+For more examples of how a test looks like check `ChangeAppRotationTest` within the `Rotation` subdirectory.
-`NonRotationTestBase` was created to make it easier to write tests that do not involve rotation (e.g., `Pip`, `split screen` or `IME`).
-Tests that inherit from the class are automatically executed twice: once in portrait and once in landscape mode and the assertions are checked independently.
-Moreover, these tests inherit the following checks:
-* all regions on the screen are covered
-* status bar is always visible
-* nav bar is always visible
-
-The default tests can be disabled by overriding the respective methods and including an `@Ignore` annotation.
-
-### Exceptional cases
-
-Tests that rotate the device should inherit from `RotationTestBase`.
-This class allows the test to be freely configured and does not provide any assertions.
-
-
-### Example
-
-Start by defining common or error prone transitions using `TransitionRunner`.
-```kotlin
-@LargeTest
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MyTest(
- beginRotationName: String,
- beginRotation: Int
-) : NonRotationTestBase(beginRotationName, beginRotation) {
- init {
- mTestApp = MyAppHelper(InstrumentationRegistry.getInstrumentation())
- }
-
- override val transitionToRun: TransitionRunner
- get() = TransitionRunner.newBuilder()
- .withTag("myTest")
- .recordAllRuns()
- .runBefore { device.pressHome() }
- .runBefore { device.waitForIdle() }
- .run { testApp.open() }
- .runAfter{ testApp.exit() }
- .repeat(2)
- .includeJankyRuns()
- .build()
-
- @Test
- fun myWMTest() {
- checkResults {
- WmTraceSubject.assertThat(it)
- .showsAppWindow(MyTestApp)
- .forAllEntries()
- }
- }
-
- @Test
- fun mySFTest() {
- checkResults {
- LayersTraceSubject.assertThat(it)
- .showsLayer(MyTestApp)
- .forAllEntries()
- }
- }
-}
-```
diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 75b4bbd3dd6b..c7da778b752b 100644
--- a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -17,14 +17,10 @@
package com.android.server.wm.flicker.rotation
import android.platform.test.annotations.Presubmit
-import android.tools.Position
-import android.tools.datatypes.Rect
import android.tools.device.apphelpers.StandardAppHelper
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.subject.layers.LayerTraceEntrySubject
-import android.tools.traces.Condition
-import android.tools.traces.DeviceStateDump
import android.tools.traces.component.ComponentNameMatcher
import android.tools.traces.component.IComponentMatcher
import android.tools.traces.surfaceflinger.Display
@@ -40,12 +36,7 @@ abstract class RotationTransition(flicker: LegacyFlickerTest) : BaseTest(flicker
override val transition: FlickerBuilder.() -> Unit = {
setup { this.setRotation(flicker.scenario.startRotation) }
teardown { testApp.exit(wmHelper) }
- transitions {
- this.setRotation(flicker.scenario.endRotation)
- wmHelper.StateSyncBuilder()
- .add(navBarInPosition(flicker.scenario.isGesturalNavigation))
- .waitForAndVerify()
- }
+ transitions { this.setRotation(flicker.scenario.endRotation) }
}
/** {@inheritDoc} */
@@ -98,37 +89,4 @@ abstract class RotationTransition(flicker: LegacyFlickerTest) : BaseTest(flicker
appLayerRotates_StartingPos()
appLayerRotates_EndingPos()
}
-
- private fun navBarInPosition(isGesturalNavigation: Boolean): Condition<DeviceStateDump> {
- return Condition("navBarPosition") { dump ->
- val display =
- dump.layerState.displays.filterNot { it.isOff }.minByOrNull { it.id }
- ?: error("There is no display!")
- val displayArea = display.layerStackSpace
- val navBarPosition = display.navBarPosition(isGesturalNavigation)
- val navBarRegion = dump.layerState
- .getLayerWithBuffer(ComponentNameMatcher.NAV_BAR)
- ?.visibleRegion?.bounds ?: Rect.EMPTY
-
- when (navBarPosition) {
- Position.TOP ->
- navBarRegion.top == displayArea.top &&
- navBarRegion.left == displayArea.left &&
- navBarRegion.right == displayArea.right
- Position.BOTTOM ->
- navBarRegion.bottom == displayArea.bottom &&
- navBarRegion.left == displayArea.left &&
- navBarRegion.right == displayArea.right
- Position.LEFT ->
- navBarRegion.left == displayArea.left &&
- navBarRegion.top == displayArea.top &&
- navBarRegion.bottom == displayArea.bottom
- Position.RIGHT ->
- navBarRegion.right == displayArea.right &&
- navBarRegion.top == displayArea.top &&
- navBarRegion.bottom == displayArea.bottom
- else -> error("Unknown position $navBarPosition")
- }
- }
- }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 8853c1db856f..348d0af5a2d3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -279,12 +279,11 @@ fun LegacyFlickerTest.snapshotStartingWindowLayerCoversExactlyOnApp(
subject.isVisible
}
val visibleAreas =
- snapshotLayers
- .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion }
+ snapshotLayers.mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion }
val snapshotRegion = RegionSubject(visibleAreas, it.timestamp)
val appVisibleRegion = it.visibleRegion(component)
// Verify the size of snapshotRegion covers appVisibleRegion exactly in animation.
- if (snapshotRegion.region.isNotEmpty && appVisibleRegion.region.isNotEmpty) {
+ if (!snapshotRegion.region.isEmpty && !appVisibleRegion.region.isEmpty) {
snapshotRegion.coversExactly(appVisibleRegion.region)
}
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
index ffed4087acff..ef8d84fb915a 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
@@ -45,13 +45,7 @@ constructor(
require(gameView != null) { "Mock game app view not found." }
val bound = gameView.getVisibleBounds()
- return uiDevice.swipe(
- bound.centerX(),
- 0,
- bound.centerX(),
- bound.centerY(),
- SWIPE_STEPS
- )
+ return uiDevice.swipe(bound.centerX(), 0, bound.centerX(), bound.centerY(), SWIPE_STEPS)
}
/**
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
index b09e53b6400d..634b6eedd7e6 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
@@ -17,8 +17,8 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.tools.datatypes.Rect
-import android.tools.datatypes.Region
+import android.graphics.Rect
+import android.graphics.Region
import android.tools.device.apphelpers.StandardAppHelper
import android.tools.helpers.FIND_TIMEOUT
import android.tools.helpers.SYSTEMUI_PACKAGE
@@ -86,7 +86,7 @@ constructor(
.add("letterboxAppRepositioned") {
val letterboxAppWindow = getWindowRegion(wmHelper)
val appRegionBounds = letterboxAppWindow.bounds
- val appWidth = appRegionBounds.width
+ val appWidth = appRegionBounds.width()
return@add if (right)
appRegionBounds.left == displayBounds.right - appWidth &&
appRegionBounds.right == displayBounds.right
@@ -108,7 +108,7 @@ constructor(
.add("letterboxAppRepositioned") {
val letterboxAppWindow = getWindowRegion(wmHelper)
val appRegionBounds = letterboxAppWindow.bounds
- val appHeight = appRegionBounds.height
+ val appHeight = appRegionBounds.height()
return@add if (bottom)
appRegionBounds.bottom == displayBounds.bottom &&
appRegionBounds.top == (displayBounds.bottom - appHeight + navBarHeight)
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index db933b30a822..43fd57bf39aa 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -18,10 +18,11 @@ package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
import android.content.Intent
+import android.graphics.Rect
+import android.graphics.Region
import android.media.session.MediaController
import android.media.session.MediaSessionManager
-import android.tools.datatypes.Rect
-import android.tools.datatypes.Region
+import android.tools.datatypes.coversMoreThan
import android.tools.device.apphelpers.StandardAppHelper
import android.tools.helpers.FIND_TIMEOUT
import android.tools.helpers.SYSTEMUI_PACKAGE
@@ -62,7 +63,7 @@ open class PipAppHelper(instrumentation: Instrumentation) :
/** Drags the PIP window to the provided final coordinates without releasing the pointer. */
fun dragPipWindowAwayFromEdgeWithoutRelease(wmHelper: WindowManagerStateHelper, steps: Int) {
- val initWindowRect = getWindowRect(wmHelper).clone()
+ val initWindowRect = Rect(getWindowRect(wmHelper))
// initial pointer at the center of the window
val initialCoord =
@@ -101,7 +102,7 @@ open class PipAppHelper(instrumentation: Instrumentation) :
* @throws IllegalStateException if default display bounds are not available
*/
fun dragPipWindowAwayFromEdge(wmHelper: WindowManagerStateHelper, steps: Int) {
- val initWindowRect = getWindowRect(wmHelper).clone()
+ val initWindowRect = Rect(getWindowRect(wmHelper))
// initial pointer at the center of the window
val startX = initWindowRect.centerX()
@@ -153,12 +154,12 @@ open class PipAppHelper(instrumentation: Instrumentation) :
val windowRect = getWindowRect(wmHelper)
// first pointer's initial x coordinate is halfway between the left edge and the center
- val initLeftX = (windowRect.centerX() - windowRect.width / 4).toFloat()
+ val initLeftX = (windowRect.centerX() - windowRect.width() / 4).toFloat()
// second pointer's initial x coordinate is halfway between the right edge and the center
- val initRightX = (windowRect.centerX() + windowRect.width / 4).toFloat()
+ val initRightX = (windowRect.centerX() + windowRect.width() / 4).toFloat()
// horizontal distance the window should increase by
- val distIncrease = windowRect.width * percent
+ val distIncrease = windowRect.width() * percent
// final x-coordinates
val finalLeftX = initLeftX - (distIncrease / 2)
@@ -183,7 +184,7 @@ open class PipAppHelper(instrumentation: Instrumentation) :
adjustedSteps
)
- waitForPipWindowToExpandFrom(wmHelper, Region.from(windowRect))
+ waitForPipWindowToExpandFrom(wmHelper, Region(windowRect))
}
/**
@@ -201,12 +202,12 @@ open class PipAppHelper(instrumentation: Instrumentation) :
val windowRect = getWindowRect(wmHelper)
// first pointer's initial x coordinate is halfway between the left edge and the center
- val initLeftX = (windowRect.centerX() - windowRect.width / 4).toFloat()
+ val initLeftX = (windowRect.centerX() - windowRect.width() / 4).toFloat()
// second pointer's initial x coordinate is halfway between the right edge and the center
- val initRightX = (windowRect.centerX() + windowRect.width / 4).toFloat()
+ val initRightX = (windowRect.centerX() + windowRect.width() / 4).toFloat()
// decrease by the distance specified through the percentage
- val distDecrease = windowRect.width * percent
+ val distDecrease = windowRect.width() * percent
// get the final x-coordinates and make sure they are not passing the center of the window
val finalLeftX = Math.min(initLeftX + (distDecrease / 2), windowRect.centerX().toFloat())
@@ -231,7 +232,7 @@ open class PipAppHelper(instrumentation: Instrumentation) :
adjustedSteps
)
- waitForPipWindowToMinimizeFrom(wmHelper, Region.from(windowRect))
+ waitForPipWindowToMinimizeFrom(wmHelper, Region(windowRect))
}
/**
@@ -375,7 +376,7 @@ open class PipAppHelper(instrumentation: Instrumentation) :
uiDevice.click(windowRect.centerX(), windowRect.centerY())
Log.d(TAG, "Wait for app transition to end")
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
- waitForPipWindowToExpandFrom(wmHelper, Region.from(windowRect))
+ waitForPipWindowToExpandFrom(wmHelper, Region(windowRect))
}
private fun waitForPipWindowToExpandFrom(
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index e60764f137af..80282c309320 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -33,7 +33,6 @@ import android.icu.util.ULocale
import android.os.Bundle
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
-import android.provider.Settings
import android.util.proto.ProtoOutputStream
import android.view.InputDevice
import android.view.inputmethod.InputMethodInfo
@@ -47,9 +46,7 @@ import com.android.test.input.R
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
-import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
-import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -234,631 +231,326 @@ class KeyboardLayoutManagerTests {
}
@Test
- fun testDefaultUi_getKeyboardLayouts() {
- NewSettingsApiFlag(false).use {
- val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
- assertNotEquals(
- "Default UI: Keyboard layout API should not return empty array",
- 0,
- keyboardLayouts.size
- )
- assertTrue(
- "Default UI: Keyboard layout API should provide English(US) layout",
- hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
- }
+ fun testGetKeyboardLayouts() {
+ val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
+ assertNotEquals(
+ "Keyboard layout API should not return empty array",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue(
+ "Keyboard layout API should provide English(US) layout",
+ hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
}
@Test
- fun testNewUi_getKeyboardLayouts() {
- NewSettingsApiFlag(true).use {
- val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
- assertNotEquals(
- "New UI: Keyboard layout API should not return empty array",
- 0,
- keyboardLayouts.size
- )
- assertTrue(
- "New UI: Keyboard layout API should provide English(US) layout",
- hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
- }
+ fun testGetKeyboardLayout() {
+ val keyboardLayout =
+ keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
+ assertEquals("getKeyboardLayout API should return correct Layout from " +
+ "available layouts",
+ ENGLISH_US_LAYOUT_DESCRIPTOR,
+ keyboardLayout!!.descriptor
+ )
}
@Test
- fun testDefaultUi_getKeyboardLayoutsForInputDevice() {
- NewSettingsApiFlag(false).use {
- val keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutsForInputDevice(keyboardDevice.identifier)
- assertNotEquals(
- "Default UI: getKeyboardLayoutsForInputDevice API should not return empty array",
- 0,
- keyboardLayouts.size
- )
- assertTrue(
- "Default UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
- "layout",
- hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
+ fun testGetSetKeyboardLayoutForInputDevice_withImeInfo() {
+ val imeSubtype = createImeSubtype()
- val vendorSpecificKeyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutsForInputDevice(
- vendorSpecificKeyboardDevice.identifier
- )
- assertEquals(
- "Default UI: getKeyboardLayoutsForInputDevice API should return only vendor " +
- "specific layout",
- 1,
- vendorSpecificKeyboardLayouts.size
- )
- assertEquals(
- "Default UI: getKeyboardLayoutsForInputDevice API should return vendor specific " +
- "layout",
- VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR,
- vendorSpecificKeyboardLayouts[0].descriptor
+ keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+ ENGLISH_UK_LAYOUT_DESCRIPTOR
+ )
+ var result =
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
)
- }
- }
+ assertEquals(
+ "getKeyboardLayoutForInputDevice API should return the set layout",
+ ENGLISH_UK_LAYOUT_DESCRIPTOR,
+ result.layoutDescriptor
+ )
- @Test
- fun testNewUi_getKeyboardLayoutsForInputDevice() {
- NewSettingsApiFlag(true).use {
- val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
- assertNotEquals(
- "New UI: getKeyboardLayoutsForInputDevice API should not return empty array",
- 0,
- keyboardLayouts.size
- )
- assertTrue(
- "New UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
- "layout",
- hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ // This should replace previously set layout
+ keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ result =
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
)
- }
+ assertEquals(
+ "getKeyboardLayoutForInputDevice API should return the last set layout",
+ ENGLISH_US_LAYOUT_DESCRIPTOR,
+ result.layoutDescriptor
+ )
}
@Test
- fun testDefaultUi_getSetCurrentKeyboardLayoutForInputDevice() {
- NewSettingsApiFlag(false).use {
- assertNull(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null if " +
- "nothing was set",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
-
- keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier,
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- val keyboardLayout =
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- assertEquals(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return the set " +
- "layout",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayout
+ fun testGetKeyboardLayoutListForInputDevice() {
+ // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts
+ var keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("hi-Latn")
+ )
+ assertNotEquals(
+ "getKeyboardLayoutListForInputDevice API should return the list of " +
+ "supported layouts with matching script code",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(US) layout for hi-Latn",
+ containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(No script code) layout for hi-Latn",
+ containsLayout(
+ keyboardLayouts,
+ createLayoutDescriptor("keyboard_layout_english_without_script_code")
)
- }
- }
+ )
- @Test
- fun testNewUi_getSetCurrentKeyboardLayoutForInputDevice() {
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier,
- ENGLISH_US_LAYOUT_DESCRIPTOR
+ // Check Layouts for "hi" which by default uses 'Deva' script.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("hi")
)
- assertNull(
- "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null " +
- "even after setCurrentKeyboardLayoutForInputDevice",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
+ assertEquals("getKeyboardLayoutListForInputDevice API should return empty " +
+ "list if no supported layouts available",
+ 0,
+ keyboardLayouts.size
+ )
- @Test
- fun testDefaultUi_getEnabledKeyboardLayoutsForInputDevice() {
- NewSettingsApiFlag(false).use {
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+ // If user manually selected some layout, always provide it in the layout list
+ val imeSubtype = createImeSubtypeForLanguageTag("hi")
+ keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ imeSubtype
)
-
- val keyboardLayouts =
- keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
- keyboardDevice.identifier
- )
- assertEquals(
- "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return added " +
- "layout",
+ assertEquals("getKeyboardLayoutListForInputDevice API should return user " +
+ "selected layout even if the script is incompatible with IME",
1,
- keyboardLayouts.size
- )
- assertEquals(
- "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return " +
- "English(US) layout",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayouts[0]
- )
- assertEquals(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
- "English(US) layout (Auto select the first enabled layout)",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
-
- keyboardLayoutManager.removeKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- assertEquals(
- "Default UI: getKeyboardLayoutsForInputDevice API should return 0 layouts",
- 0,
- keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
- keyboardDevice.identifier
- ).size
- )
- assertNull(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null after " +
- "the enabled layout is removed",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
-
- @Test
- fun testNewUi_getEnabledKeyboardLayoutsForInputDevice() {
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
- )
-
- assertEquals(
- "New UI: getEnabledKeyboardLayoutsForInputDevice API should return always return " +
- "an empty array",
- 0,
- keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
- keyboardDevice.identifier
- ).size
- )
- assertNull(
- "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
-
- @Test
- fun testDefaultUi_switchKeyboardLayout() {
- NewSettingsApiFlag(false).use {
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- assertEquals(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
- "English(US) layout",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
-
- keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
-
- // Throws null pointer because trying to show toast using TestLooper
- assertThrows(NullPointerException::class.java) { testLooper.dispatchAll() }
- assertEquals("Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
- "English(UK) layout",
- ENGLISH_UK_LAYOUT_DESCRIPTOR,
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
-
- @Test
- fun testNewUi_switchKeyboardLayout() {
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
-
- keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
- testLooper.dispatchAll()
-
- assertNull("New UI: getCurrentKeyboardLayoutForInputDevice API should always return " +
- "null",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
-
- @Test
- fun testDefaultUi_getKeyboardLayout() {
- NewSettingsApiFlag(false).use {
- val keyboardLayout =
- keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
- assertEquals("Default UI: getKeyboardLayout API should return correct Layout from " +
- "available layouts",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayout!!.descriptor
- )
- }
- }
-
- @Test
- fun testNewUi_getKeyboardLayout() {
- NewSettingsApiFlag(true).use {
- val keyboardLayout =
- keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
- assertEquals("New UI: getKeyboardLayout API should return correct Layout from " +
- "available layouts",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayout!!.descriptor
- )
- }
- }
-
- @Test
- fun testDefaultUi_getSetKeyboardLayoutForInputDevice_WithImeInfo() {
- NewSettingsApiFlag(false).use {
- val imeSubtype = createImeSubtype()
- keyboardLayoutManager.setKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
- ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- assertEquals(
- "Default UI: getKeyboardLayoutForInputDevice API should always return " +
- "KeyboardLayoutSelectionResult.FAILED",
- KeyboardLayoutSelectionResult.FAILED,
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
- )
- )
- }
- }
-
- @Test
- fun testNewUi_getSetKeyboardLayoutForInputDevice_withImeInfo() {
- NewSettingsApiFlag(true).use {
- val imeSubtype = createImeSubtype()
-
- keyboardLayoutManager.setKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
- ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- var result =
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
- )
- assertEquals(
- "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
- ENGLISH_UK_LAYOUT_DESCRIPTOR,
- result.layoutDescriptor
- )
-
- // This should replace previously set layout
- keyboardLayoutManager.setKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- result =
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
- )
- assertEquals(
- "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- result.layoutDescriptor
- )
- }
- }
-
- @Test
- fun testDefaultUi_getKeyboardLayoutListForInputDevice() {
- NewSettingsApiFlag(false).use {
- val keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtype()
- )
- assertEquals("Default UI: getKeyboardLayoutListForInputDevice API should always " +
- "return empty array",
- 0,
- keyboardLayouts.size
- )
- }
- }
+ keyboardLayouts.size
+ )
- @Test
- fun testNewUi_getKeyboardLayoutListForInputDevice() {
- NewSettingsApiFlag(true).use {
- // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts
- var keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ // Special case Japanese: UScript ignores provided script code for certain language tags
+ // Should manually match provided script codes and then rely on Uscript to derive
+ // script from language tags and match those.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("hi-Latn")
- )
- assertNotEquals(
- "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
- "supported layouts with matching script code",
- 0,
- keyboardLayouts.size
- )
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
- "containing English(US) layout for hi-Latn",
- containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ createImeSubtypeForLanguageTag("ja-Latn-JP")
)
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
- "containing English(No script code) layout for hi-Latn",
- containsLayout(
- keyboardLayouts,
- createLayoutDescriptor("keyboard_layout_english_without_script_code")
- )
+ assertNotEquals(
+ "getKeyboardLayoutListForInputDevice API should return the list of " +
+ "supported layouts with matching script code for ja-Latn-JP",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(US) layout for ja-Latn-JP",
+ containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(No script code) layout for ja-Latn-JP",
+ containsLayout(
+ keyboardLayouts,
+ createLayoutDescriptor("keyboard_layout_english_without_script_code")
)
+ )
- // Check Layouts for "hi" which by default uses 'Deva' script.
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ // If script code not explicitly provided for Japanese should rely on Uscript to find
+ // derived script code and hence no suitable layout will be found.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("hi")
- )
- assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return empty " +
- "list if no supported layouts available",
- 0,
- keyboardLayouts.size
- )
-
- // If user manually selected some layout, always provide it in the layout list
- val imeSubtype = createImeSubtypeForLanguageTag("hi")
- keyboardLayoutManager.setKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- imeSubtype
- )
- assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return user " +
- "selected layout even if the script is incompatible with IME",
- 1,
- keyboardLayouts.size
- )
-
- // Special case Japanese: UScript ignores provided script code for certain language tags
- // Should manually match provided script codes and then rely on Uscript to derive
- // script from language tags and match those.
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("ja-Latn-JP")
- )
- assertNotEquals(
- "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
- "supported layouts with matching script code for ja-Latn-JP",
- 0,
- keyboardLayouts.size
- )
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
- "containing English(US) layout for ja-Latn-JP",
- containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
- "containing English(No script code) layout for ja-Latn-JP",
- containsLayout(
- keyboardLayouts,
- createLayoutDescriptor("keyboard_layout_english_without_script_code")
- )
- )
-
- // If script code not explicitly provided for Japanese should rely on Uscript to find
- // derived script code and hence no suitable layout will be found.
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("ja-JP")
- )
- assertEquals(
- "New UI: getKeyboardLayoutListForInputDevice API should return empty list of " +
- "supported layouts with matching script code for ja-JP",
- 0,
- keyboardLayouts.size
- )
-
- // If IME doesn't have a corresponding language tag, then should show all available
- // layouts no matter the script code.
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, null
- )
- assertNotEquals(
- "New UI: getKeyboardLayoutListForInputDevice API should return all layouts if" +
- "language tag or subtype not provided",
- 0,
- keyboardLayouts.size
- )
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Latin " +
- "layouts if language tag or subtype not provided",
- containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ createImeSubtypeForLanguageTag("ja-JP")
)
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Cyrillic " +
- "layouts if language tag or subtype not provided",
- containsLayout(
- keyboardLayouts,
- createLayoutDescriptor("keyboard_layout_russian")
- )
- )
- }
- }
+ assertEquals(
+ "getKeyboardLayoutListForInputDevice API should return empty list of " +
+ "supported layouts with matching script code for ja-JP",
+ 0,
+ keyboardLayouts.size
+ )
- @Test
- fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTag() {
- NewSettingsApiFlag(true).use {
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("en-US"),
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("en-GB"),
- ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("de"),
- GERMAN_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("fr-FR"),
- createLayoutDescriptor("keyboard_layout_french")
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("ru"),
+ // If IME doesn't have a corresponding language tag, then should show all available
+ // layouts no matter the script code.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, null
+ )
+ assertNotEquals(
+ "getKeyboardLayoutListForInputDevice API should return all layouts if" +
+ "language tag or subtype not provided",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should contain Latin " +
+ "layouts if language tag or subtype not provided",
+ containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should contain Cyrillic " +
+ "layouts if language tag or subtype not provided",
+ containsLayout(
+ keyboardLayouts,
createLayoutDescriptor("keyboard_layout_russian")
)
- assertEquals(
- "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
- "KeyboardLayoutSelectionResult.FAILED when no layout available",
- KeyboardLayoutSelectionResult.FAILED,
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("it")
- )
- )
- assertEquals(
- "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
- "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
- "available",
- KeyboardLayoutSelectionResult.FAILED,
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("en-Deva")
- )
- )
- }
+ )
}
@Test
- fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTagAndLayoutType() {
- NewSettingsApiFlag(true).use {
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-US", "qwerty"),
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-US", "dvorak"),
- createLayoutDescriptor("keyboard_layout_english_us_dvorak")
- )
- // Try to match layout type even if country doesn't match
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-GB", "dvorak"),
- createLayoutDescriptor("keyboard_layout_english_us_dvorak")
- )
- // Choose layout based on layout type priority, if layout type is not provided by IME
- // (Qwerty > Dvorak > Extended)
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-US", ""),
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-GB", "qwerty"),
- ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"),
- GERMAN_LAYOUT_DESCRIPTOR
- )
- // Wrong layout type should match with language if provided layout type not available
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"),
- GERMAN_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("fr-FR", "azerty"),
- createLayoutDescriptor("keyboard_layout_french")
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("ru", "qwerty"),
- createLayoutDescriptor("keyboard_layout_russian_qwerty")
- )
- // If layout type is empty then prioritize KCM with empty layout type
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
- createLayoutDescriptor("keyboard_layout_russian")
+ fun testGetDefaultKeyboardLayoutForInputDevice_withImeLanguageTag() {
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("en-US"),
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("en-GB"),
+ ENGLISH_UK_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("de"),
+ GERMAN_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("fr-FR"),
+ createLayoutDescriptor("keyboard_layout_french")
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("ru"),
+ createLayoutDescriptor("keyboard_layout_russian")
+ )
+ assertEquals(
+ "getDefaultKeyboardLayoutForInputDevice should return " +
+ "KeyboardLayoutSelectionResult.FAILED when no layout available",
+ KeyboardLayoutSelectionResult.FAILED,
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("it")
)
- assertEquals("New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+ )
+ assertEquals(
+ "getDefaultKeyboardLayoutForInputDevice should return " +
"KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
"available",
- KeyboardLayoutSelectionResult.FAILED,
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
- )
+ KeyboardLayoutSelectionResult.FAILED,
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("en-Deva")
)
- }
+ )
}
@Test
- fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
- NewSettingsApiFlag(true).use {
- val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty")
- // Should return English dvorak even if IME current layout is French, since HW says the
- // keyboard is a Dvorak keyboard
- assertCorrectLayout(
- englishDvorakKeyboardDevice,
- frenchSubtype,
- createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+ fun testGetDefaultKeyboardLayoutForInputDevice_withImeLanguageTagAndLayoutType() {
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-US", "qwerty"),
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-US", "dvorak"),
+ createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+ )
+ // Try to match layout type even if country doesn't match
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-GB", "dvorak"),
+ createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+ )
+ // Choose layout based on layout type priority, if layout type is not provided by IME
+ // (Qwerty > Dvorak > Extended)
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-US", ""),
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-GB", "qwerty"),
+ ENGLISH_UK_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"),
+ GERMAN_LAYOUT_DESCRIPTOR
+ )
+ // Wrong layout type should match with language if provided layout type not available
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"),
+ GERMAN_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("fr-FR", "azerty"),
+ createLayoutDescriptor("keyboard_layout_french")
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("ru", "qwerty"),
+ createLayoutDescriptor("keyboard_layout_russian_qwerty")
+ )
+ // If layout type is empty then prioritize KCM with empty layout type
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
+ createLayoutDescriptor("keyboard_layout_russian")
+ )
+ assertEquals("getDefaultKeyboardLayoutForInputDevice should return " +
+ "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
+ "available",
+ KeyboardLayoutSelectionResult.FAILED,
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
)
+ )
+ }
- // Back to back changing HW keyboards with same product and vendor ID but different
- // language and layout type should configure the layouts correctly.
- assertCorrectLayout(
- englishQwertyKeyboardDevice,
- frenchSubtype,
- createLayoutDescriptor("keyboard_layout_english_us")
- )
+ @Test
+ fun testGetDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
+ val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty")
+ // Should return English dvorak even if IME current layout is French, since HW says the
+ // keyboard is a Dvorak keyboard
+ assertCorrectLayout(
+ englishDvorakKeyboardDevice,
+ frenchSubtype,
+ createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+ )
- // Fallback to IME information if the HW provided layout script is incompatible with the
- // provided IME subtype
- assertCorrectLayout(
- englishDvorakKeyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
- createLayoutDescriptor("keyboard_layout_russian")
- )
- }
+ // Back to back changing HW keyboards with same product and vendor ID but different
+ // language and layout type should configure the layouts correctly.
+ assertCorrectLayout(
+ englishQwertyKeyboardDevice,
+ frenchSubtype,
+ createLayoutDescriptor("keyboard_layout_english_us")
+ )
+
+ // Fallback to IME information if the HW provided layout script is incompatible with the
+ // provided IME subtype
+ assertCorrectLayout(
+ englishDvorakKeyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
+ createLayoutDescriptor("keyboard_layout_russian")
+ )
}
@Test
@@ -867,27 +559,25 @@ class KeyboardLayoutManagerTests {
KeyboardLayoutManager.ImeInfo(0, imeInfo,
createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz")))
Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
- ExtendedMockito.verify {
- FrameworkStatsLog.write(
- ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.eq(keyboardDevice.vendorId),
- ArgumentMatchers.eq(keyboardDevice.productId),
- ArgumentMatchers.eq(
- createByteArray(
- KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
- LAYOUT_TYPE_DEFAULT,
- GERMAN_LAYOUT_NAME,
- KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
- "de-Latn",
- LAYOUT_TYPE_QWERTZ
- ),
+ keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.eq(keyboardDevice.vendorId),
+ ArgumentMatchers.eq(keyboardDevice.productId),
+ ArgumentMatchers.eq(
+ createByteArray(
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ LAYOUT_TYPE_DEFAULT,
+ GERMAN_LAYOUT_NAME,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+ "de-Latn",
+ LAYOUT_TYPE_QWERTZ
),
- ArgumentMatchers.eq(keyboardDevice.deviceBus),
- )
- }
+ ),
+ ArgumentMatchers.eq(keyboardDevice.deviceBus),
+ )
}
}
@@ -897,27 +587,25 @@ class KeyboardLayoutManagerTests {
KeyboardLayoutManager.ImeInfo(0, imeInfo,
createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz")))
Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceAdded(englishQwertyKeyboardDevice.id)
- ExtendedMockito.verify {
- FrameworkStatsLog.write(
- ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
- ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
- ArgumentMatchers.eq(
- createByteArray(
- "en",
- LAYOUT_TYPE_QWERTY,
- ENGLISH_US_LAYOUT_NAME,
- KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
- "de-Latn",
- LAYOUT_TYPE_QWERTZ
- )
- ),
- ArgumentMatchers.eq(keyboardDevice.deviceBus),
- )
- }
+ keyboardLayoutManager.onInputDeviceAdded(englishQwertyKeyboardDevice.id)
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
+ ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
+ ArgumentMatchers.eq(
+ createByteArray(
+ "en",
+ LAYOUT_TYPE_QWERTY,
+ ENGLISH_US_LAYOUT_NAME,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ "de-Latn",
+ LAYOUT_TYPE_QWERTZ
+ )
+ ),
+ ArgumentMatchers.eq(keyboardDevice.deviceBus),
+ )
}
}
@@ -925,27 +613,25 @@ class KeyboardLayoutManagerTests {
fun testConfigurationLogged_onInputDeviceAdded_DefaultSelection() {
val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
- ExtendedMockito.verify {
- FrameworkStatsLog.write(
- ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.eq(keyboardDevice.vendorId),
- ArgumentMatchers.eq(keyboardDevice.productId),
- ArgumentMatchers.eq(
- createByteArray(
- KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
- LAYOUT_TYPE_DEFAULT,
- "Default",
- KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT,
- KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
- LAYOUT_TYPE_DEFAULT
- ),
+ keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.eq(keyboardDevice.vendorId),
+ ArgumentMatchers.eq(keyboardDevice.productId),
+ ArgumentMatchers.eq(
+ createByteArray(
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ LAYOUT_TYPE_DEFAULT,
+ "Default",
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT,
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ LAYOUT_TYPE_DEFAULT
),
- ArgumentMatchers.eq(keyboardDevice.deviceBus),
- )
- }
+ ),
+ ArgumentMatchers.eq(keyboardDevice.deviceBus),
+ )
}
}
@@ -953,19 +639,17 @@ class KeyboardLayoutManagerTests {
fun testConfigurationNotLogged_onInputDeviceChanged() {
val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
- ExtendedMockito.verify({
- FrameworkStatsLog.write(
- ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any(ByteArray::class.java),
- ArgumentMatchers.anyInt(),
- )
- }, Mockito.times(0))
- }
+ keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+ ExtendedMockito.verify({
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(ByteArray::class.java),
+ ArgumentMatchers.anyInt(),
+ )
+ }, Mockito.times(0))
}
@Test
@@ -975,18 +659,16 @@ class KeyboardLayoutManagerTests {
Mockito.doReturn(false).`when`(keyboardLayoutManager).isVirtualDevice(
ArgumentMatchers.eq(keyboardDevice.id)
)
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
- ExtendedMockito.verify(
- notificationManager,
- Mockito.times(1)
- ).notifyAsUser(
- ArgumentMatchers.isNull(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any(),
- ArgumentMatchers.any()
- )
- }
+ keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+ ExtendedMockito.verify(
+ notificationManager,
+ Mockito.times(1)
+ ).notifyAsUser(
+ ArgumentMatchers.isNull(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any()
+ )
}
@Test
@@ -996,18 +678,16 @@ class KeyboardLayoutManagerTests {
Mockito.doReturn(true).`when`(keyboardLayoutManager).isVirtualDevice(
ArgumentMatchers.eq(keyboardDevice.id)
)
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
- ExtendedMockito.verify(
- notificationManager,
- Mockito.never()
- ).notifyAsUser(
- ArgumentMatchers.isNull(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any(),
- ArgumentMatchers.any()
- )
- }
+ keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+ ExtendedMockito.verify(
+ notificationManager,
+ Mockito.never()
+ ).notifyAsUser(
+ ArgumentMatchers.isNull(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any()
+ )
}
private fun assertCorrectLayout(
@@ -1019,7 +699,7 @@ class KeyboardLayoutManagerTests {
device.identifier, USER_ID, imeInfo, imeSubtype
)
assertEquals(
- "New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
+ "getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
expectedLayout,
result.layoutDescriptor
)
@@ -1123,21 +803,4 @@ class KeyboardLayoutManagerTests {
info.serviceInfo.name = RECEIVER_NAME
return info
}
-
- private inner class NewSettingsApiFlag constructor(enabled: Boolean) : AutoCloseable {
- init {
- Settings.Global.putString(
- context.contentResolver,
- "settings_new_keyboard_ui", enabled.toString()
- )
- }
-
- override fun close() {
- Settings.Global.putString(
- context.contentResolver,
- "settings_new_keyboard_ui",
- ""
- )
- }
- }
}
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 093923f3ed53..1fdf97a4c821 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -45,13 +45,13 @@ import android.os.test.TestLooper;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.util.AtomicFile;
-import android.util.LongArrayQueue;
import android.util.Xml;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
import androidx.test.InstrumentationRegistry;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.PackageWatchdog.HealthCheckState;
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
index b506d74d6500..56845aeb6a2c 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
@@ -21,10 +21,9 @@ import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTE
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -34,9 +33,7 @@ import android.hardware.usb.IUsbOperationInternal;
import android.hardware.usb.flags.Flags;
import android.os.RemoteException;
import android.os.UserManager;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.runner.AndroidJUnit4;
@@ -64,7 +61,7 @@ public class UsbServiceTest {
@Mock
private UsbSettingsManager mUsbSettingsManager;
@Mock
- private IUsbOperationInternal mIUsbOperationInternal;
+ private IUsbOperationInternal mCallback;
private static final String TEST_PORT_ID = "123";
@@ -77,98 +74,105 @@ public class UsbServiceTest {
private UsbService mUsbService;
@Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING);
MockitoAnnotations.initMocks(this);
- mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager, mUserManager,
- mUsbSettingsManager);
+
+ when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(), eq(TEST_TRANSACTION_ID),
+ eq(mCallback), any())).thenReturn(true);
+
+ mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager,
+ mUserManager, mUsbSettingsManager);
+ }
+
+ private void assertToggleUsbSuccessfully(int uid, boolean enable) {
+ assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
+ TEST_TRANSACTION_ID, mCallback, uid));
+
+ verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
+ enable, TEST_TRANSACTION_ID, mCallback, null);
+ verifyZeroInteractions(mCallback);
+
+ clearInvocations(mUsbPortManager);
+ clearInvocations(mCallback);
+ }
+
+ private void assertToggleUsbFailed(int uid, boolean enable) throws Exception {
+ assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
+ TEST_TRANSACTION_ID, mCallback, uid));
+
+ verifyZeroInteractions(mUsbPortManager);
+ verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+
+ clearInvocations(mUsbPortManager);
+ clearInvocations(mCallback);
}
/**
* Verify enableUsbData successfully disables USB port without error
*/
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
- public void usbPort_SuccessfullyDisabled() {
- boolean enableState = false;
- when(mUsbPortManager.enableUsbData(TEST_PORT_ID, enableState, TEST_TRANSACTION_ID,
- mIUsbOperationInternal, null)).thenReturn(true);
-
- assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enableState,
- TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_FIRST_CALLER_ID));
-
- verify(mUsbPortManager, times(1)).enableUsbData(TEST_PORT_ID,
- enableState, TEST_TRANSACTION_ID, mIUsbOperationInternal, null);
- verifyZeroInteractions(mIUsbOperationInternal);
+ public void disableUsb_successfullyDisable() {
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
}
/**
* Verify enableUsbData successfully enables USB port without error given no other stakers
*/
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
- public void usbPortWhenNoOtherStakers_SuccessfullyEnabledUsb() {
- boolean enableState = true;
- when(mUsbPortManager.enableUsbData(TEST_PORT_ID, enableState, TEST_TRANSACTION_ID,
- mIUsbOperationInternal, null))
- .thenReturn(true);
-
- assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enableState,
- TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_FIRST_CALLER_ID));
- verifyZeroInteractions(mIUsbOperationInternal);
+ public void enableUsbWhenNoOtherStakers_successfullyEnable() {
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
}
/**
* Verify enableUsbData does not enable USB port if other stakers are present
*/
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
- public void usbPortWithOtherStakers_DoesNotToEnableUsb() throws RemoteException {
- mUsbService.enableUsbDataInternal(TEST_PORT_ID, false, TEST_TRANSACTION_ID,
- mIUsbOperationInternal, TEST_FIRST_CALLER_ID);
- clearInvocations(mUsbPortManager);
+ public void enableUsbPortWithOtherStakers_failsToEnable() throws Exception {
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
- assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, true,
- TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_SECOND_CALLER_ID));
+ assertToggleUsbFailed(TEST_SECOND_CALLER_ID, true);
+ }
- verifyZeroInteractions(mUsbPortManager);
- verify(mIUsbOperationInternal).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+ /**
+ * Verify enableUsbData successfully enables USB port when the last staker is removed
+ */
+ @Test
+ public void enableUsbByTheOnlyStaker_successfullyEnable() {
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
}
/**
* Verify enableUsbDataWhileDockedInternal does not enable USB port if other stakers are present
*/
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
- public void enableUsbWhileDockedWhenThereAreOtherStakers_DoesNotEnableUsb()
+ public void enableUsbWhileDockedWhenThereAreOtherStakers_failsToEnable()
throws RemoteException {
- mUsbService.enableUsbDataInternal(TEST_PORT_ID, false, TEST_TRANSACTION_ID,
- mIUsbOperationInternal, TEST_FIRST_CALLER_ID);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
- mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, 0,
- mIUsbOperationInternal, TEST_SECOND_CALLER_ID);
+ mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
+ mCallback, TEST_SECOND_CALLER_ID);
- verify(mUsbPortManager, never()).enableUsbDataWhileDocked(any(),
- anyLong(), any(), any());
- verify(mIUsbOperationInternal).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+ verifyZeroInteractions(mUsbPortManager);
+ verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
}
/**
- * Verify enableUsbDataWhileDockedInternal does enable USB port if other stakers are
+ * Verify enableUsbDataWhileDockedInternal does enable USB port if other stakers are
* not present
*/
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
- public void enableUsbWhileDockedWhenThereAreNoStakers_SuccessfullyEnableUsb()
- throws RemoteException {
+ public void enableUsbWhileDockedWhenThereAreNoStakers_SuccessfullyEnable() {
mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
- mIUsbOperationInternal, TEST_SECOND_CALLER_ID);
+ mCallback, TEST_SECOND_CALLER_ID);
- verify(mUsbPortManager, times(1))
- .enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID,
- mIUsbOperationInternal, null);
- verifyZeroInteractions(mIUsbOperationInternal);
+ verify(mUsbPortManager).enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID,
+ mCallback, null);
+ verifyZeroInteractions(mCallback);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
index c1c520e99cac..b98161dd26fb 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
@@ -59,7 +59,8 @@ public class AslConverter {
switch (format) {
case HUMAN_READABLE:
Element appMetadataBundles =
- XmlUtils.getSingleElement(document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES);
+ XmlUtils.getSingleChildElement(
+ document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES, true);
return new AndroidSafetyLabelFactory()
.createFromHrElements(XmlUtils.listOf(appMetadataBundles));
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
index 112b92c9aebb..ecfad91a378f 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
@@ -61,4 +61,10 @@ public class AndroidSafetyLabel implements AslMarshallable {
}
return XmlUtils.listOf(aslEle);
}
+
+ /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+ @Override
+ public List<Element> toHrDomElements(Document doc) {
+ return List.of();
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
index b69c30f7f522..41ce6e55e989 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
@@ -55,4 +55,11 @@ public class AndroidSafetyLabelFactory implements AslMarshallableFactory<Android
return new AndroidSafetyLabel(
version, systemAppSafetyLabel, safetyLabels, transparencyInfo);
}
+
+ /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+ @Override
+ public AndroidSafetyLabel createFromOdElements(List<Element> elements)
+ throws MalformedXmlException {
+ return null;
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
index 3f1ddebefe99..21f328d8e2d0 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
@@ -142,4 +142,10 @@ public class AppInfo implements AslMarshallable {
}
return XmlUtils.listOf(appInfoEle);
}
+
+ /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+ @Override
+ public List<Element> toHrDomElements(Document doc) {
+ return List.of();
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
index 59a437d7ece5..6fcf637fe069 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
@@ -72,4 +72,10 @@ public class AppInfoFactory implements AslMarshallableFactory<AppInfo> {
email,
website);
}
+
+ /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+ @Override
+ public AppInfo createFromOdElements(List<Element> elements) throws MalformedXmlException {
+ return null;
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
index 48747ccbcff6..0a70e7d0d74b 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
@@ -23,6 +23,9 @@ import java.util.List;
public interface AslMarshallable {
- /** Creates the on-device DOM element from the AslMarshallable Java Object. */
+ /** Creates the on-device DOM elements from the AslMarshallable Java Object. */
List<Element> toOdDomElements(Document doc);
+
+ /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+ List<Element> toHrDomElements(Document doc);
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
index a49b3e77155b..39582900f3a0 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
@@ -24,6 +24,9 @@ import java.util.List;
public interface AslMarshallableFactory<T extends AslMarshallable> {
- /** Creates an {@link AslMarshallableFactory} from human-readable DOM element */
+ /** Creates an {@link AslMarshallableFactory} from human-readable DOM elements */
T createFromHrElements(List<Element> elements) throws MalformedXmlException;
+
+ /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+ T createFromOdElements(List<Element> elements) throws MalformedXmlException;
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
index 4d67162b442d..eb975540ce70 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
@@ -59,4 +59,10 @@ public class DataCategory implements AslMarshallable {
}
return XmlUtils.listOf(dataCategoryEle);
}
+
+ /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+ @Override
+ public List<Element> toHrDomElements(Document doc) {
+ return List.of();
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
index 37d99e7ef85e..90424fe00504 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
@@ -50,4 +50,31 @@ public class DataCategoryFactory implements AslMarshallableFactory<DataCategory>
return new DataCategory(categoryName, dataTypeMap);
}
+
+ /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+ @Override
+ public DataCategory createFromOdElements(List<Element> elements) throws MalformedXmlException {
+ Element dataCategoryEle = XmlUtils.getSingleElement(elements);
+ Map<String, DataType> dataTypeMap = new LinkedHashMap<String, DataType>();
+ String categoryName = dataCategoryEle.getAttribute(XmlUtils.OD_ATTR_NAME);
+ var odDataTypes = XmlUtils.asElementList(dataCategoryEle.getChildNodes());
+ for (Element odDataTypeEle : odDataTypes) {
+ String dataTypeName = odDataTypeEle.getAttribute(XmlUtils.OD_ATTR_NAME);
+ if (!DataTypeConstants.getValidDataTypes().containsKey(categoryName)) {
+ throw new MalformedXmlException(
+ String.format("Unrecognized data category %s", categoryName));
+ }
+ if (!DataTypeConstants.getValidDataTypes().get(categoryName).contains(dataTypeName)) {
+ throw new MalformedXmlException(
+ String.format(
+ "Unrecognized data type name %s for category %s",
+ dataTypeName, categoryName));
+ }
+ dataTypeMap.put(
+ dataTypeName,
+ new DataTypeFactory().createFromOdElements(XmlUtils.listOf(odDataTypeEle)));
+ }
+
+ return new DataCategory(categoryName, dataTypeMap);
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
index 7516faf9f77a..4a0d75977d78 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
@@ -79,6 +79,16 @@ public class DataLabels implements AslMarshallable {
return XmlUtils.listOf(dataLabelsEle);
}
+ /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+ @Override
+ public List<Element> toHrDomElements(Document doc) {
+ Element dataLabelsEle = doc.createElement(XmlUtils.HR_TAG_DATA_LABELS);
+ maybeAppendHrDataUsages(doc, dataLabelsEle, mDataAccessed, XmlUtils.HR_TAG_DATA_ACCESSED);
+ maybeAppendHrDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.HR_TAG_DATA_COLLECTED);
+ maybeAppendHrDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.HR_TAG_DATA_SHARED);
+ return XmlUtils.listOf(dataLabelsEle);
+ }
+
private void maybeAppendDataUsages(
Document doc,
Element dataLabelsEle,
@@ -100,4 +110,42 @@ public class DataLabels implements AslMarshallable {
}
dataLabelsEle.appendChild(dataUsageEle);
}
+
+ private void maybeAppendHrDataUsages(
+ Document doc,
+ Element dataLabelsEle,
+ Map<String, DataCategory> dataCategoriesMap,
+ String dataUsageTypeName) {
+ if (dataCategoriesMap.isEmpty()) {
+ return;
+ }
+ for (String dataCategoryName : dataCategoriesMap.keySet()) {
+ DataCategory dataCategory = dataCategoriesMap.get(dataCategoryName);
+ for (String dataTypeName : dataCategory.getDataTypes().keySet()) {
+ DataType dataType = dataCategory.getDataTypes().get(dataTypeName);
+ // XmlUtils.appendChildren(dataLabelsEle, dataType.toHrDomElements(doc));
+ Element hrDataTypeEle = doc.createElement(dataUsageTypeName);
+ hrDataTypeEle.setAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY, dataCategoryName);
+ hrDataTypeEle.setAttribute(XmlUtils.HR_ATTR_DATA_TYPE, dataTypeName);
+ XmlUtils.maybeSetHrBoolAttr(
+ hrDataTypeEle,
+ XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL,
+ dataType.getIsCollectionOptional());
+ XmlUtils.maybeSetHrBoolAttr(
+ hrDataTypeEle,
+ XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL,
+ dataType.getIsSharingOptional());
+ XmlUtils.maybeSetHrBoolAttr(
+ hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL, dataType.getEphemeral());
+ hrDataTypeEle.setAttribute(
+ XmlUtils.HR_ATTR_PURPOSES,
+ String.join(
+ "|",
+ dataType.getPurposes().stream()
+ .map(DataType.Purpose::toString)
+ .toList()));
+ dataLabelsEle.appendChild(hrDataTypeEle);
+ }
+ }
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
index dc77fd08aa53..5473e010cc65 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
@@ -22,7 +22,6 @@ import com.android.asllib.util.MalformedXmlException;
import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -46,9 +45,82 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED);
Map<String, DataCategory> dataShared =
getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_SHARED);
+ DataLabels dataLabels = new DataLabels(dataAccessed, dataCollected, dataShared);
+ validateIsXOptional(dataLabels);
+ return dataLabels;
+ }
+
+ /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+ @Override
+ public DataLabels createFromOdElements(List<Element> elements) throws MalformedXmlException {
+ Element dataLabelsEle = XmlUtils.getSingleElement(elements);
+ if (dataLabelsEle == null) {
+ AslgenUtil.logI("Found no DataLabels in od format.");
+ return null;
+ }
+ Map<String, DataCategory> dataAccessed =
+ getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_ACCESSED);
+ Map<String, DataCategory> dataCollected =
+ getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_COLLECTED);
+ Map<String, DataCategory> dataShared =
+ getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_SHARED);
+ DataLabels dataLabels = new DataLabels(dataAccessed, dataCollected, dataShared);
+ validateIsXOptional(dataLabels);
+ return dataLabels;
+ }
+
+ private static Map<String, DataCategory> getOdDataCategoriesWithTag(
+ Element dataLabelsEle, String dataCategoryUsageTypeTag) throws MalformedXmlException {
+ Map<String, DataCategory> dataCategoryMap = new LinkedHashMap<String, DataCategory>();
+ Element dataUsageEle =
+ XmlUtils.getOdPbundleWithName(dataLabelsEle, dataCategoryUsageTypeTag, false);
+ if (dataUsageEle == null) {
+ return dataCategoryMap;
+ }
+ List<Element> dataCategoryEles = XmlUtils.asElementList(dataUsageEle.getChildNodes());
+ for (Element dataCategoryEle : dataCategoryEles) {
+ String dataCategoryName = dataCategoryEle.getAttribute(XmlUtils.OD_ATTR_NAME);
+ DataCategory dataCategory =
+ new DataCategoryFactory().createFromOdElements(List.of(dataCategoryEle));
+ dataCategoryMap.put(dataCategoryName, dataCategory);
+ }
+ return dataCategoryMap;
+ }
+ private static Map<String, DataCategory> getDataCategoriesWithTag(
+ Element dataLabelsEle, String dataCategoryUsageTypeTag) throws MalformedXmlException {
+ List<Element> dataUsedElements =
+ XmlUtils.getChildrenByTagName(dataLabelsEle, dataCategoryUsageTypeTag);
+ Map<String, DataCategory> dataCategoryMap = new LinkedHashMap<String, DataCategory>();
+
+ Set<String> dataCategoryNames = new HashSet<String>();
+ for (int i = 0; i < dataUsedElements.size(); i++) {
+ Element dataUsedEle = dataUsedElements.get(i);
+ String dataCategoryName = dataUsedEle.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY);
+ if (!DataCategoryConstants.getValidDataCategories().contains(dataCategoryName)) {
+ throw new MalformedXmlException(
+ String.format("Unrecognized category name: %s", dataCategoryName));
+ }
+ dataCategoryNames.add(dataCategoryName);
+ }
+ for (String dataCategoryName : dataCategoryNames) {
+ var dataCategoryElements =
+ dataUsedElements.stream()
+ .filter(
+ ele ->
+ ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY)
+ .equals(dataCategoryName))
+ .toList();
+ DataCategory dataCategory =
+ new DataCategoryFactory().createFromHrElements(dataCategoryElements);
+ dataCategoryMap.put(dataCategoryName, dataCategory);
+ }
+ return dataCategoryMap;
+ }
+
+ private void validateIsXOptional(DataLabels dataLabels) throws MalformedXmlException {
// Validate booleans such as isCollectionOptional, isSharingOptional.
- for (DataCategory dataCategory : dataAccessed.values()) {
+ for (DataCategory dataCategory : dataLabels.getDataAccessed().values()) {
for (DataType dataType : dataCategory.getDataTypes().values()) {
if (dataType.getIsSharingOptional() != null) {
throw new MalformedXmlException(
@@ -66,7 +138,7 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
}
}
}
- for (DataCategory dataCategory : dataCollected.values()) {
+ for (DataCategory dataCategory : dataLabels.getDataCollected().values()) {
for (DataType dataType : dataCategory.getDataTypes().values()) {
if (dataType.getIsSharingOptional() != null) {
throw new MalformedXmlException(
@@ -77,7 +149,7 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
}
}
}
- for (DataCategory dataCategory : dataShared.values()) {
+ for (DataCategory dataCategory : dataLabels.getDataShared().values()) {
for (DataType dataType : dataCategory.getDataTypes().values()) {
if (dataType.getIsCollectionOptional() != null) {
throw new MalformedXmlException(
@@ -88,37 +160,5 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
}
}
}
-
- return new DataLabels(dataAccessed, dataCollected, dataShared);
- }
-
- private static Map<String, DataCategory> getDataCategoriesWithTag(
- Element dataLabelsEle, String dataCategoryUsageTypeTag) throws MalformedXmlException {
- NodeList dataUsedNodeList = dataLabelsEle.getElementsByTagName(dataCategoryUsageTypeTag);
- Map<String, DataCategory> dataCategoryMap = new LinkedHashMap<String, DataCategory>();
-
- Set<String> dataCategoryNames = new HashSet<String>();
- for (int i = 0; i < dataUsedNodeList.getLength(); i++) {
- Element dataUsedEle = (Element) dataUsedNodeList.item(i);
- String dataCategoryName = dataUsedEle.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY);
- if (!DataCategoryConstants.getValidDataCategories().contains(dataCategoryName)) {
- throw new MalformedXmlException(
- String.format("Unrecognized category name: %s", dataCategoryName));
- }
- dataCategoryNames.add(dataCategoryName);
- }
- for (String dataCategoryName : dataCategoryNames) {
- var dataCategoryElements =
- XmlUtils.asElementList(dataUsedNodeList).stream()
- .filter(
- ele ->
- ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY)
- .equals(dataCategoryName))
- .toList();
- DataCategory dataCategory =
- new DataCategoryFactory().createFromHrElements(dataCategoryElements);
- dataCategoryMap.put(dataCategoryName, dataCategory);
- }
- return dataCategoryMap;
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
index 347136237966..02b7189c09ba 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
@@ -160,6 +160,12 @@ public class DataType implements AslMarshallable {
return XmlUtils.listOf(dataTypeEle);
}
+ /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+ @Override
+ public List<Element> toHrDomElements(Document doc) {
+ return List.of();
+ }
+
private static void maybeAddBoolToOdElement(
Document doc, Element parentEle, Boolean b, String odName) {
if (b == null) {
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
index ed434cda0823..488c2595912a 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
@@ -51,4 +51,31 @@ public class DataTypeFactory implements AslMarshallableFactory<DataType> {
return new DataType(
dataTypeName, purposes, isCollectionOptional, isSharingOptional, ephemeral);
}
+
+ /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+ @Override
+ public DataType createFromOdElements(List<Element> elements) throws MalformedXmlException {
+ Element odDataTypeEle = XmlUtils.getSingleElement(elements);
+ String dataTypeName = odDataTypeEle.getAttribute(XmlUtils.OD_ATTR_NAME);
+ List<Integer> purposeInts =
+ XmlUtils.getOdIntArray(odDataTypeEle, XmlUtils.OD_NAME_PURPOSES, true);
+ List<DataType.Purpose> purposes =
+ purposeInts.stream().map(DataType.Purpose::forValue).collect(Collectors.toList());
+ if (purposes.isEmpty()) {
+ throw new MalformedXmlException(String.format("Found no purpose in: %s", dataTypeName));
+ }
+ if (new HashSet<>(purposes).size() != purposes.size()) {
+ throw new MalformedXmlException(
+ String.format("Found non-unique purposes in: %s", dataTypeName));
+ }
+ Boolean isCollectionOptional =
+ XmlUtils.getOdBoolEle(
+ odDataTypeEle, XmlUtils.OD_NAME_IS_COLLECTION_OPTIONAL, false);
+ Boolean isSharingOptional =
+ XmlUtils.getOdBoolEle(odDataTypeEle, XmlUtils.OD_NAME_IS_SHARING_OPTIONAL, false);
+ Boolean ephemeral = XmlUtils.getOdBoolEle(odDataTypeEle, XmlUtils.OD_NAME_EPHEMERAL, false);
+
+ return new DataType(
+ dataTypeName, purposes, isCollectionOptional, isSharingOptional, ephemeral);
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
index 382a1f0d0eca..efdc8d0a5f11 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
@@ -139,4 +139,10 @@ public class DeveloperInfo implements AslMarshallable {
return XmlUtils.listOf(developerInfoEle);
}
+
+ /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+ @Override
+ public List<Element> toHrDomElements(Document doc) {
+ return List.of();
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
index b5310bac232a..c3e7ac35c545 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
@@ -57,4 +57,10 @@ public class DeveloperInfoFactory implements AslMarshallableFactory<DeveloperInf
website,
appDeveloperRegistryId);
}
+
+ /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+ @Override
+ public DeveloperInfo createFromOdElements(List<Element> elements) throws MalformedXmlException {
+ return null;
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
index 22c3fd8f2a1c..576820dac6c6 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
@@ -28,10 +28,18 @@ public class SafetyLabels implements AslMarshallable {
private final Long mVersion;
private final DataLabels mDataLabels;
+ private final SecurityLabels mSecurityLabels;
+ private final ThirdPartyVerification mThirdPartyVerification;
- public SafetyLabels(Long version, DataLabels dataLabels) {
+ public SafetyLabels(
+ Long version,
+ DataLabels dataLabels,
+ SecurityLabels securityLabels,
+ ThirdPartyVerification thirdPartyVerification) {
this.mVersion = version;
this.mDataLabels = dataLabels;
+ this.mSecurityLabels = securityLabels;
+ this.mThirdPartyVerification = thirdPartyVerification;
}
/** Returns the data label for the safety label */
@@ -54,6 +62,18 @@ public class SafetyLabels implements AslMarshallable {
if (mDataLabels != null) {
XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toOdDomElements(doc));
}
+ if (mSecurityLabels != null) {
+ XmlUtils.appendChildren(safetyLabelsEle, mSecurityLabels.toOdDomElements(doc));
+ }
+ if (mThirdPartyVerification != null) {
+ XmlUtils.appendChildren(safetyLabelsEle, mThirdPartyVerification.toOdDomElements(doc));
+ }
return XmlUtils.listOf(safetyLabelsEle);
}
+
+ /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+ @Override
+ public List<Element> toHrDomElements(Document doc) {
+ return List.of();
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
index 6bf8ef3df32d..7e1838f40680 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
@@ -44,6 +44,28 @@ public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels>
safetyLabelsEle,
XmlUtils.HR_TAG_DATA_LABELS,
false)));
- return new SafetyLabels(version, dataLabels);
+ SecurityLabels securityLabels =
+ new SecurityLabelsFactory()
+ .createFromHrElements(
+ XmlUtils.listOf(
+ XmlUtils.getSingleChildElement(
+ safetyLabelsEle,
+ XmlUtils.HR_TAG_SECURITY_LABELS,
+ false)));
+ ThirdPartyVerification thirdPartyVerification =
+ new ThirdPartyVerificationFactory()
+ .createFromHrElements(
+ XmlUtils.listOf(
+ XmlUtils.getSingleChildElement(
+ safetyLabelsEle,
+ XmlUtils.HR_TAG_THIRD_PARTY_VERIFICATION,
+ false)));
+ return new SafetyLabels(version, dataLabels, securityLabels, thirdPartyVerification);
+ }
+
+ /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+ @Override
+ public SafetyLabels createFromOdElements(List<Element> elements) throws MalformedXmlException {
+ return null;
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
new file mode 100644
index 000000000000..437343b14605
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
@@ -0,0 +1,59 @@
+/*
+ * 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.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+/** Security Labels representation */
+public class SecurityLabels implements AslMarshallable {
+
+ private final Boolean mIsDataDeletable;
+ private final Boolean mIsDataEncrypted;
+
+ public SecurityLabels(Boolean isDataDeletable, Boolean isDataEncrypted) {
+ this.mIsDataDeletable = isDataDeletable;
+ this.mIsDataEncrypted = isDataEncrypted;
+ }
+
+ /** Creates an on-device DOM element from the {@link SecurityLabels}. */
+ @Override
+ public List<Element> toOdDomElements(Document doc) {
+ Element ele = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SECURITY_LABELS);
+ if (mIsDataDeletable != null) {
+ ele.appendChild(
+ XmlUtils.createOdBooleanEle(
+ doc, XmlUtils.OD_NAME_IS_DATA_DELETABLE, mIsDataDeletable));
+ }
+ if (mIsDataEncrypted != null) {
+ ele.appendChild(
+ XmlUtils.createOdBooleanEle(
+ doc, XmlUtils.OD_NAME_IS_DATA_ENCRYPTED, mIsDataEncrypted));
+ }
+ return XmlUtils.listOf(ele);
+ }
+
+ /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+ @Override
+ public List<Element> toHrDomElements(Document doc) {
+ return List.of();
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
new file mode 100644
index 000000000000..9dc4712c33b0
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
@@ -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.asllib.marshallable;
+
+import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public class SecurityLabelsFactory implements AslMarshallableFactory<SecurityLabels> {
+
+ /** Creates a {@link SecurityLabels} from the human-readable DOM element. */
+ @Override
+ public SecurityLabels createFromHrElements(List<Element> elements)
+ throws MalformedXmlException {
+ Element ele = XmlUtils.getSingleElement(elements);
+ if (ele == null) {
+ AslgenUtil.logI("No SecurityLabels found in hr format.");
+ return null;
+ }
+ Boolean isDataDeletable =
+ XmlUtils.getBoolAttr(ele, XmlUtils.HR_ATTR_IS_DATA_DELETABLE, false);
+ Boolean isDataEncrypted =
+ XmlUtils.getBoolAttr(ele, XmlUtils.HR_ATTR_IS_DATA_ENCRYPTED, false);
+ return new SecurityLabels(isDataDeletable, isDataEncrypted);
+ }
+
+ /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+ @Override
+ public SecurityLabels createFromOdElements(List<Element> elements)
+ throws MalformedXmlException {
+ return null;
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
index 595d748b59af..f0ecf93f2805 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
@@ -46,4 +46,10 @@ public class SystemAppSafetyLabel implements AslMarshallable {
XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl));
return XmlUtils.listOf(systemAppSafetyLabelEle);
}
+
+ /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+ @Override
+ public List<Element> toHrDomElements(Document doc) {
+ return List.of();
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
index f99955993d6c..5b7fe32f2735 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
@@ -39,4 +39,11 @@ public class SystemAppSafetyLabelFactory implements AslMarshallableFactory<Syste
String url = XmlUtils.getStringAttr(systemAppSafetyLabelEle, XmlUtils.HR_ATTR_URL);
return new SystemAppSafetyLabel(url);
}
+
+ /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+ @Override
+ public SystemAppSafetyLabel createFromOdElements(List<Element> elements)
+ throws MalformedXmlException {
+ return null;
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
new file mode 100644
index 000000000000..229b00243e0a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
@@ -0,0 +1,49 @@
+/*
+ * 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.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+/** ThirdPartyVerification representation. */
+public class ThirdPartyVerification implements AslMarshallable {
+
+ private final String mUrl;
+
+ public ThirdPartyVerification(String url) {
+ this.mUrl = url;
+ }
+
+ /** Creates an on-device DOM element from the {@link ThirdPartyVerification}. */
+ @Override
+ public List<Element> toOdDomElements(Document doc) {
+ Element ele =
+ XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_THIRD_PARTY_VERIFICATION);
+ ele.appendChild(XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl));
+ return XmlUtils.listOf(ele);
+ }
+
+ /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+ @Override
+ public List<Element> toHrDomElements(Document doc) {
+ return List.of();
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
new file mode 100644
index 000000000000..ac4d3836bcbd
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
@@ -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.asllib.marshallable;
+
+import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public class ThirdPartyVerificationFactory
+ implements AslMarshallableFactory<ThirdPartyVerification> {
+
+ /** Creates a {@link ThirdPartyVerification} from the human-readable DOM element. */
+ @Override
+ public ThirdPartyVerification createFromHrElements(List<Element> elements)
+ throws MalformedXmlException {
+ Element ele = XmlUtils.getSingleElement(elements);
+ if (ele == null) {
+ AslgenUtil.logI("No ThirdPartyVerification found in hr format.");
+ return null;
+ }
+
+ String url = XmlUtils.getStringAttr(ele, XmlUtils.HR_ATTR_URL);
+ return new ThirdPartyVerification(url);
+ }
+
+ /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+ @Override
+ public ThirdPartyVerification createFromOdElements(List<Element> elements)
+ throws MalformedXmlException {
+ return null;
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
index ddd3557616ca..ce7ef16ea54e 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
@@ -57,4 +57,10 @@ public class TransparencyInfo implements AslMarshallable {
}
return XmlUtils.listOf(transparencyInfoEle);
}
+
+ /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+ @Override
+ public List<Element> toHrDomElements(Document doc) {
+ return List.of();
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
index d9c2af41fcac..123de01e57ba 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
@@ -49,4 +49,11 @@ public class TransparencyInfoFactory implements AslMarshallableFactory<Transpare
return new TransparencyInfo(developerInfo, appInfo);
}
+
+ /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+ @Override
+ public TransparencyInfo createFromOdElements(List<Element> elements)
+ throws MalformedXmlException {
+ return null;
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
index 691f92fca54b..4f21b0c0ffad 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
@@ -16,8 +16,10 @@
package com.android.asllib.util;
+
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.ArrayList;
@@ -32,6 +34,8 @@ public class XmlUtils {
public static final String HR_TAG_DEVELOPER_INFO = "developer-info";
public static final String HR_TAG_APP_INFO = "app-info";
public static final String HR_TAG_DATA_LABELS = "data-labels";
+ public static final String HR_TAG_SECURITY_LABELS = "security-labels";
+ public static final String HR_TAG_THIRD_PARTY_VERIFICATION = "third-party-verification";
public static final String HR_TAG_DATA_ACCESSED = "data-accessed";
public static final String HR_TAG_DATA_COLLECTED = "data-collected";
public static final String HR_TAG_DATA_SHARED = "data-shared";
@@ -46,6 +50,8 @@ public class XmlUtils {
public static final String HR_ATTR_DATA_TYPE = "dataType";
public static final String HR_ATTR_IS_COLLECTION_OPTIONAL = "isCollectionOptional";
public static final String HR_ATTR_IS_SHARING_OPTIONAL = "isSharingOptional";
+ public static final String HR_ATTR_IS_DATA_DELETABLE = "isDataDeletable";
+ public static final String HR_ATTR_IS_DATA_ENCRYPTED = "isDataEncrypted";
public static final String HR_ATTR_EPHEMERAL = "ephemeral";
public static final String HR_ATTR_PURPOSES = "purposes";
public static final String HR_ATTR_VERSION = "version";
@@ -98,6 +104,8 @@ public class XmlUtils {
public static final String OD_NAME_VERSION = "version";
public static final String OD_NAME_URL = "url";
public static final String OD_NAME_SYSTEM_APP_SAFETY_LABEL = "system_app_safety_label";
+ public static final String OD_NAME_SECURITY_LABELS = "security_labels";
+ public static final String OD_NAME_THIRD_PARTY_VERIFICATION = "third_party_verification";
public static final String OD_NAME_DATA_LABELS = "data_labels";
public static final String OD_NAME_DATA_ACCESSED = "data_accessed";
public static final String OD_NAME_DATA_COLLECTED = "data_collected";
@@ -105,64 +113,44 @@ public class XmlUtils {
public static final String OD_NAME_PURPOSES = "purposes";
public static final String OD_NAME_IS_COLLECTION_OPTIONAL = "is_collection_optional";
public static final String OD_NAME_IS_SHARING_OPTIONAL = "is_sharing_optional";
+ public static final String OD_NAME_IS_DATA_DELETABLE = "is_data_deletable";
+ public static final String OD_NAME_IS_DATA_ENCRYPTED = "is_data_encrypted";
public static final String OD_NAME_EPHEMERAL = "ephemeral";
public static final String TRUE_STR = "true";
public static final String FALSE_STR = "false";
- /** Gets the single top-level {@link Element} having the {@param tagName}. */
- public static Element getSingleElement(Document doc, String tagName)
- throws MalformedXmlException {
- var elements = doc.getElementsByTagName(tagName);
- return getSingleElement(elements, tagName);
+ /** Gets the top-level children with the tag name.. */
+ public static List<Element> getChildrenByTagName(Node parentEle, String tagName) {
+ var elements = XmlUtils.asElementList(parentEle.getChildNodes());
+ return elements.stream().filter(e -> e.getTagName().equals(tagName)).toList();
}
/**
* Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
*/
- public static Element getSingleChildElement(Element parentEle, String tagName)
+ public static Element getSingleChildElement(Node parentEle, String tagName, boolean required)
throws MalformedXmlException {
- var elements = parentEle.getElementsByTagName(tagName);
- return getSingleElement(elements, tagName, true);
- }
+ String parentTagNameForErrorMsg =
+ (parentEle instanceof Element) ? ((Element) parentEle).getTagName() : "Node";
+ var elements = getChildrenByTagName(parentEle, tagName);
- /**
- * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
- */
- public static Element getSingleChildElement(Element parentEle, String tagName, boolean required)
- throws MalformedXmlException {
- var elements = parentEle.getElementsByTagName(tagName);
- return getSingleElement(elements, tagName, required);
- }
-
- /** Gets the single {@link Element} from {@param elements} */
- public static Element getSingleElement(NodeList elements, String tagName)
- throws MalformedXmlException {
- return getSingleElement(elements, tagName, true);
- }
-
- /** Gets the single {@link Element} from {@param elements} */
- public static Element getSingleElement(NodeList elements, String tagName, boolean required)
- throws MalformedXmlException {
- if (elements.getLength() > 1) {
+ if (elements.size() > 1) {
throw new MalformedXmlException(
String.format(
- "Expected 1 element \"%s\" in NodeList but got %s.",
- tagName, elements.getLength()));
- } else if (elements.getLength() == 0) {
+ "Expected 1 %s in %s but got %s.",
+ tagName, parentTagNameForErrorMsg, elements.size()));
+ } else if (elements.isEmpty()) {
if (required) {
throw new MalformedXmlException(
- String.format("Found no element \"%s\" in NodeList.", tagName));
+ String.format(
+ "Expected 1 %s in %s but got 0.",
+ tagName, parentTagNameForErrorMsg));
} else {
return null;
}
}
- var elementAsNode = elements.item(0);
- if (!(elementAsNode instanceof Element)) {
- throw new MalformedXmlException(
- String.format("%s was not a valid XML element.", tagName));
- }
- return ((Element) elementAsNode);
+ return elements.get(0);
}
/** Gets the single {@link Element} within {@param elements}. */
@@ -221,6 +209,13 @@ public class XmlUtils {
return ele;
}
+ /** Sets human-readable bool attribute if non-null. */
+ public static void maybeSetHrBoolAttr(Element ele, String attrName, Boolean b) {
+ if (b != null) {
+ ele.setAttribute(attrName, String.valueOf(b));
+ }
+ }
+
/** Create an on-device Long DOM Element with the given attribute name. */
public static Element createOdLongEle(Document doc, String name, long l) {
var ele = doc.createElement(XmlUtils.OD_TAG_LONG);
@@ -295,6 +290,57 @@ public class XmlUtils {
return b;
}
+ /** Gets a Boolean attribute. */
+ public static Boolean getOdBoolEle(Element ele, String nameName, boolean required)
+ throws MalformedXmlException {
+ List<Element> boolEles =
+ XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_BOOLEAN).stream()
+ .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
+ .toList();
+ if (boolEles.size() > 1) {
+ throw new MalformedXmlException(
+ String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
+ }
+ if (boolEles.isEmpty()) {
+ if (required) {
+ throw new MalformedXmlException(
+ String.format("Found no %s in %s.", nameName, ele.getTagName()));
+ }
+ return null;
+ }
+ Element boolEle = boolEles.get(0);
+
+ Boolean b = XmlUtils.fromString(boolEle.getAttribute(XmlUtils.OD_ATTR_VALUE));
+ if (b == null && required) {
+ throw new MalformedXmlException(
+ String.format(
+ "Boolean %s was required but missing, in %s.",
+ nameName, ele.getTagName()));
+ }
+ return b;
+ }
+
+ /** Gets a OD Pbundle Element attribute with the specified name. */
+ public static Element getOdPbundleWithName(Element ele, String nameName, boolean required)
+ throws MalformedXmlException {
+ List<Element> eles =
+ XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_PBUNDLE_AS_MAP).stream()
+ .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
+ .toList();
+ if (eles.size() > 1) {
+ throw new MalformedXmlException(
+ String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
+ }
+ if (eles.isEmpty()) {
+ if (required) {
+ throw new MalformedXmlException(
+ String.format("Found no %s in %s.", nameName, ele.getTagName()));
+ }
+ return null;
+ }
+ return eles.get(0);
+ }
+
/** Gets a required String attribute. */
public static String getStringAttr(Element ele, String attrName) throws MalformedXmlException {
return getStringAttr(ele, attrName, true);
@@ -317,6 +363,33 @@ public class XmlUtils {
return s;
}
+ /** Gets on-device style int array. */
+ public static List<Integer> getOdIntArray(Element ele, String nameName, boolean required)
+ throws MalformedXmlException {
+ List<Element> intArrayEles =
+ XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_INT_ARRAY).stream()
+ .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
+ .toList();
+ if (intArrayEles.size() > 1) {
+ throw new MalformedXmlException(
+ String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
+ }
+ if (intArrayEles.isEmpty()) {
+ if (required) {
+ throw new MalformedXmlException(
+ String.format("Found no %s in %s.", nameName, ele.getTagName()));
+ }
+ return List.of();
+ }
+ Element intArrayEle = intArrayEles.get(0);
+ List<Element> itemEles = XmlUtils.getChildrenByTagName(intArrayEle, XmlUtils.OD_TAG_ITEM);
+ List<Integer> ints = new ArrayList<Integer>();
+ for (Element itemEle : itemEles) {
+ ints.add(Integer.parseInt(XmlUtils.getStringAttr(itemEle, XmlUtils.OD_ATTR_VALUE)));
+ }
+ return ints;
+ }
+
/**
* Utility method for making a List from one element, to support easier refactoring if needed.
* For example, List.of() doesn't support null elements.
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
index 03e8ac6d11c0..54c80f6f7345 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
@@ -17,9 +17,15 @@
package com.android.asllib;
import com.android.asllib.marshallable.AndroidSafetyLabelTest;
+import com.android.asllib.marshallable.AppInfoTest;
import com.android.asllib.marshallable.DataCategoryTest;
import com.android.asllib.marshallable.DataLabelsTest;
import com.android.asllib.marshallable.DeveloperInfoTest;
+import com.android.asllib.marshallable.SafetyLabelsTest;
+import com.android.asllib.marshallable.SecurityLabelsTest;
+import com.android.asllib.marshallable.SystemAppSafetyLabelTest;
+import com.android.asllib.marshallable.ThirdPartyVerificationTest;
+import com.android.asllib.marshallable.TransparencyInfoTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@@ -28,8 +34,14 @@ import org.junit.runners.Suite;
@Suite.SuiteClasses({
AslgenTests.class,
AndroidSafetyLabelTest.class,
- DeveloperInfoTest.class,
+ AppInfoTest.class,
DataCategoryTest.class,
DataLabelsTest.class,
+ DeveloperInfoTest.class,
+ SafetyLabelsTest.class,
+ SecurityLabelsTest.class,
+ SystemAppSafetyLabelTest.class,
+ ThirdPartyVerificationTest.class,
+ TransparencyInfoTest.class
})
public class AllTests {}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
index 5f43008d3dc6..e2588d7bb3e7 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
@@ -34,7 +34,8 @@ import java.util.List;
@RunWith(JUnit4.class)
public class AslgenTests {
private static final String VALID_MAPPINGS_PATH = "com/android/asllib/validmappings";
- private static final List<String> VALID_MAPPINGS_SUBDIRS = List.of("location", "contacts");
+ private static final List<String> VALID_MAPPINGS_SUBDIRS =
+ List.of("location", "contacts", "general");
private static final String HR_XML_FILENAME = "hr.xml";
private static final String OD_XML_FILENAME = "od.xml";
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
index 2be447e182b2..6f6f2545a5d2 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
@@ -41,6 +41,30 @@ public class DataLabelsTest {
private static final String SHARED_INVALID_BOOL_FILE_NAME =
"data-labels-shared-invalid-bool.xml";
+ private static final String ACTIONS_IN_APP_FILE_NAME = "data-category-actions-in-app.xml";
+ private static final String APP_PERFORMANCE_FILE_NAME = "data-category-app-performance.xml";
+ private static final String AUDIO_FILE_NAME = "data-category-audio.xml";
+ private static final String CALENDAR_FILE_NAME = "data-category-calendar.xml";
+ private static final String CONTACTS_FILE_NAME = "data-category-contacts.xml";
+ private static final String EMAIL_TEXT_MESSAGE_FILE_NAME =
+ "data-category-email-text-message.xml";
+ private static final String FINANCIAL_FILE_NAME = "data-category-financial.xml";
+ private static final String HEALTH_FITNESS_FILE_NAME = "data-category-health-fitness.xml";
+ private static final String IDENTIFIERS_FILE_NAME = "data-category-identifiers.xml";
+ private static final String LOCATION_FILE_NAME = "data-category-location.xml";
+ private static final String PERSONAL_FILE_NAME = "data-category-personal.xml";
+ private static final String PERSONAL_PARTIAL_FILE_NAME = "data-category-personal-partial.xml";
+ private static final String PHOTO_VIDEO_FILE_NAME = "data-category-photo-video.xml";
+ private static final String SEARCH_AND_BROWSING_FILE_NAME =
+ "data-category-search-and-browsing.xml";
+ private static final String STORAGE_FILE_NAME = "data-category-storage.xml";
+ private static final String PERSONAL_MISSING_PURPOSE_FILE_NAME =
+ "data-category-personal-missing-purpose.xml";
+ private static final String PERSONAL_EMPTY_PURPOSE_FILE_NAME =
+ "data-category-personal-empty-purpose.xml";
+ private static final String UNRECOGNIZED_FILE_NAME = "data-category-unrecognized.xml";
+ private static final String UNRECOGNIZED_TYPE_FILE_NAME = "data-category-unrecognized-type.xml";
+
private Document mDoc = null;
@Before
@@ -54,6 +78,7 @@ public class DataLabelsTest {
public void testDataLabelsAccessedValidBool() throws Exception {
System.out.println("starting testDataLabelsAccessedValidBool.");
testHrToOdDataLabels(ACCESSED_VALID_BOOL_FILE_NAME);
+ testOdToHrDataLabels(ACCESSED_VALID_BOOL_FILE_NAME);
}
/** Test for data labels accessed invalid bool. */
@@ -68,6 +93,7 @@ public class DataLabelsTest {
public void testDataLabelsCollectedValidBool() throws Exception {
System.out.println("starting testDataLabelsCollectedValidBool.");
testHrToOdDataLabels(COLLECTED_VALID_BOOL_FILE_NAME);
+ testOdToHrDataLabels(COLLECTED_VALID_BOOL_FILE_NAME);
}
/** Test for data labels collected invalid bool. */
@@ -75,6 +101,7 @@ public class DataLabelsTest {
public void testDataLabelsCollectedInvalidBool() throws Exception {
System.out.println("starting testDataLabelsCollectedInvalidBool.");
hrToOdExpectException(COLLECTED_INVALID_BOOL_FILE_NAME);
+ odToHrExpectException(COLLECTED_INVALID_BOOL_FILE_NAME);
}
/** Test for data labels shared valid bool. */
@@ -82,6 +109,7 @@ public class DataLabelsTest {
public void testDataLabelsSharedValidBool() throws Exception {
System.out.println("starting testDataLabelsSharedValidBool.");
testHrToOdDataLabels(SHARED_VALID_BOOL_FILE_NAME);
+ testOdToHrDataLabels(SHARED_VALID_BOOL_FILE_NAME);
}
/** Test for data labels shared invalid bool. */
@@ -91,12 +119,207 @@ public class DataLabelsTest {
hrToOdExpectException(SHARED_INVALID_BOOL_FILE_NAME);
}
+ /* Data categories bidirectional tests... */
+
+ /** Test for data labels actions in app. */
+ @Test
+ public void testDataLabelsActionsInApp() throws Exception {
+ System.out.println("starting testDataLabelsActionsInApp.");
+ testHrToOdDataLabels(ACTIONS_IN_APP_FILE_NAME);
+ testOdToHrDataLabels(ACTIONS_IN_APP_FILE_NAME);
+ }
+
+ /** Test for data labels app performance. */
+ @Test
+ public void testDataLabelsAppPerformance() throws Exception {
+ System.out.println("starting testDataLabelsAppPerformance.");
+ testHrToOdDataLabels(APP_PERFORMANCE_FILE_NAME);
+ testOdToHrDataLabels(APP_PERFORMANCE_FILE_NAME);
+ }
+
+ /** Test for data labels audio. */
+ @Test
+ public void testDataLabelsAudio() throws Exception {
+ System.out.println("starting testDataLabelsAudio.");
+ testHrToOdDataLabels(AUDIO_FILE_NAME);
+ testOdToHrDataLabels(AUDIO_FILE_NAME);
+ }
+
+ /** Test for data labels calendar. */
+ @Test
+ public void testDataLabelsCalendar() throws Exception {
+ System.out.println("starting testDataLabelsCalendar.");
+ testHrToOdDataLabels(CALENDAR_FILE_NAME);
+ testOdToHrDataLabels(CALENDAR_FILE_NAME);
+ }
+
+ /** Test for data labels contacts. */
+ @Test
+ public void testDataLabelsContacts() throws Exception {
+ System.out.println("starting testDataLabelsContacts.");
+ testHrToOdDataLabels(CONTACTS_FILE_NAME);
+ testOdToHrDataLabels(CONTACTS_FILE_NAME);
+ }
+
+ /** Test for data labels email text message. */
+ @Test
+ public void testDataLabelsEmailTextMessage() throws Exception {
+ System.out.println("starting testDataLabelsEmailTextMessage.");
+ testHrToOdDataLabels(EMAIL_TEXT_MESSAGE_FILE_NAME);
+ testOdToHrDataLabels(EMAIL_TEXT_MESSAGE_FILE_NAME);
+ }
+
+ /** Test for data labels financial. */
+ @Test
+ public void testDataLabelsFinancial() throws Exception {
+ System.out.println("starting testDataLabelsFinancial.");
+ testHrToOdDataLabels(FINANCIAL_FILE_NAME);
+ testOdToHrDataLabels(FINANCIAL_FILE_NAME);
+ }
+
+ /** Test for data labels health fitness. */
+ @Test
+ public void testDataLabelsHealthFitness() throws Exception {
+ System.out.println("starting testDataLabelsHealthFitness.");
+ testHrToOdDataLabels(HEALTH_FITNESS_FILE_NAME);
+ testOdToHrDataLabels(HEALTH_FITNESS_FILE_NAME);
+ }
+
+ /** Test for data labels identifiers. */
+ @Test
+ public void testDataLabelsIdentifiers() throws Exception {
+ System.out.println("starting testDataLabelsIdentifiers.");
+ testHrToOdDataLabels(IDENTIFIERS_FILE_NAME);
+ testOdToHrDataLabels(IDENTIFIERS_FILE_NAME);
+ }
+
+ /** Test for data labels location. */
+ @Test
+ public void testDataLabelsLocation() throws Exception {
+ System.out.println("starting testDataLabelsLocation.");
+ testHrToOdDataLabels(LOCATION_FILE_NAME);
+ testOdToHrDataLabels(LOCATION_FILE_NAME);
+ }
+
+ /** Test for data labels personal. */
+ @Test
+ public void testDataLabelsPersonal() throws Exception {
+ System.out.println("starting testDataLabelsPersonal.");
+ testHrToOdDataLabels(PERSONAL_FILE_NAME);
+ testOdToHrDataLabels(PERSONAL_FILE_NAME);
+ }
+
+ /** Test for data labels personal partial. */
+ @Test
+ public void testDataLabelsPersonalPartial() throws Exception {
+ System.out.println("starting testDataLabelsPersonalPartial.");
+ testHrToOdDataLabels(PERSONAL_PARTIAL_FILE_NAME);
+ testOdToHrDataLabels(PERSONAL_PARTIAL_FILE_NAME);
+ }
+
+ /** Test for data labels photo video. */
+ @Test
+ public void testDataLabelsPhotoVideo() throws Exception {
+ System.out.println("starting testDataLabelsPhotoVideo.");
+ testHrToOdDataLabels(PHOTO_VIDEO_FILE_NAME);
+ testOdToHrDataLabels(PHOTO_VIDEO_FILE_NAME);
+ }
+
+ /** Test for data labels search and browsing. */
+ @Test
+ public void testDataLabelsSearchAndBrowsing() throws Exception {
+ System.out.println("starting testDataLabelsSearchAndBrowsing.");
+ testHrToOdDataLabels(SEARCH_AND_BROWSING_FILE_NAME);
+ testOdToHrDataLabels(SEARCH_AND_BROWSING_FILE_NAME);
+ }
+
+ /** Test for data labels storage. */
+ @Test
+ public void testDataLabelsStorage() throws Exception {
+ System.out.println("starting testDataLabelsStorage.");
+ testHrToOdDataLabels(STORAGE_FILE_NAME);
+ testOdToHrDataLabels(STORAGE_FILE_NAME);
+ }
+
+ /** Test for data labels hr unrecognized data category. */
+ @Test
+ public void testDataLabelsHrUnrecognizedDataCategory() throws Exception {
+ System.out.println("starting testDataLabelsHrUnrecognizedDataCategory.");
+ hrToOdExpectException(UNRECOGNIZED_FILE_NAME);
+ }
+
+ /** Test for data labels hr unrecognized data type. */
+ @Test
+ public void testDataLabelsHrUnrecognizedDataType() throws Exception {
+ System.out.println("starting testDataLabelsHrUnrecognizedDataType.");
+ hrToOdExpectException(UNRECOGNIZED_TYPE_FILE_NAME);
+ }
+
+ /** Test for data labels hr missing purpose. */
+ @Test
+ public void testDataLabelsHrMissingPurpose() throws Exception {
+ System.out.println("starting testDataLabelsHrMissingPurpose.");
+ hrToOdExpectException(PERSONAL_MISSING_PURPOSE_FILE_NAME);
+ }
+
+ /** Test for data labels hr empty purpose. */
+ @Test
+ public void testDataLabelsHrEmptyPurpose() throws Exception {
+ System.out.println("starting testDataLabelsHrEmptyPurpose.");
+ hrToOdExpectException(PERSONAL_EMPTY_PURPOSE_FILE_NAME);
+ }
+
+ /** Test for data labels od unrecognized data category. */
+ @Test
+ public void testDataLabelsOdUnrecognizedDataCategory() throws Exception {
+ System.out.println("starting testDataLabelsOdUnrecognizedDataCategory.");
+ odToHrExpectException(UNRECOGNIZED_FILE_NAME);
+ }
+
+ /** Test for data labels od unrecognized data type. */
+ @Test
+ public void testDataLabelsOdUnrecognizedDataType() throws Exception {
+ System.out.println("starting testDataLabelsOdUnrecognizedDataCategory.");
+ odToHrExpectException(UNRECOGNIZED_TYPE_FILE_NAME);
+ }
+
+ /** Test for data labels od missing purpose. */
+ @Test
+ public void testDataLabelsOdMissingPurpose() throws Exception {
+ System.out.println("starting testDataLabelsOdMissingPurpose.");
+ odToHrExpectException(PERSONAL_MISSING_PURPOSE_FILE_NAME);
+ }
+
+ /** Test for data labels od empty purpose. */
+ @Test
+ public void testDataLabelsOdEmptyPurpose() throws Exception {
+ System.out.println("starting testDataLabelsOdEmptyPurpose.");
+ odToHrExpectException(PERSONAL_EMPTY_PURPOSE_FILE_NAME);
+ }
+
private void hrToOdExpectException(String fileName) {
TestUtils.hrToOdExpectException(new DataLabelsFactory(), DATA_LABELS_HR_PATH, fileName);
}
+ private void odToHrExpectException(String fileName) {
+ TestUtils.odToHrExpectException(new DataLabelsFactory(), DATA_LABELS_OD_PATH, fileName);
+ }
+
private void testHrToOdDataLabels(String fileName) throws Exception {
TestUtils.testHrToOd(
- mDoc, new DataLabelsFactory(), DATA_LABELS_HR_PATH, DATA_LABELS_OD_PATH, fileName);
+ TestUtils.document(),
+ new DataLabelsFactory(),
+ DATA_LABELS_HR_PATH,
+ DATA_LABELS_OD_PATH,
+ fileName);
+ }
+
+ private void testOdToHrDataLabels(String fileName) throws Exception {
+ TestUtils.testOdToHr(
+ TestUtils.document(),
+ new DataLabelsFactory(),
+ DATA_LABELS_OD_PATH,
+ DATA_LABELS_HR_PATH,
+ fileName);
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
index b62620ef417e..c52d6c873646 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
@@ -32,6 +32,9 @@ public class SafetyLabelsTest {
private static final String MISSING_VERSION_FILE_NAME = "missing-version.xml";
private static final String VALID_EMPTY_FILE_NAME = "valid-empty.xml";
private static final String WITH_DATA_LABELS_FILE_NAME = "with-data-labels.xml";
+ private static final String WITH_SECURITY_LABELS_FILE_NAME = "with-security-labels.xml";
+ private static final String WITH_THIRD_PARTY_VERIFICATION_FILE_NAME =
+ "with-third-party-verification.xml";
private Document mDoc = null;
@@ -62,6 +65,20 @@ public class SafetyLabelsTest {
testHrToOdSafetyLabels(WITH_DATA_LABELS_FILE_NAME);
}
+ /** Test for safety labels with security labels. */
+ @Test
+ public void testSafetyLabelsWithSecurityLabels() throws Exception {
+ System.out.println("starting testSafetyLabelsWithSecurityLabels.");
+ testHrToOdSafetyLabels(WITH_SECURITY_LABELS_FILE_NAME);
+ }
+
+ /** Test for safety labels with third party verification. */
+ @Test
+ public void testSafetyLabelsWithThirdPartyVerification() throws Exception {
+ System.out.println("starting testSafetyLabelsWithThirdPartyVerification.");
+ testHrToOdSafetyLabels(WITH_THIRD_PARTY_VERIFICATION_FILE_NAME);
+ }
+
private void hrToOdExpectException(String fileName) {
TestUtils.hrToOdExpectException(new SafetyLabelsFactory(), SAFETY_LABELS_HR_PATH, fileName);
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
new file mode 100644
index 000000000000..c0d0d728f762
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib.marshallable;
+
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+import java.nio.file.Paths;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class SecurityLabelsTest {
+ private static final String SECURITY_LABELS_HR_PATH = "com/android/asllib/securitylabels/hr";
+ private static final String SECURITY_LABELS_OD_PATH = "com/android/asllib/securitylabels/od";
+
+ public static final List<String> OPTIONAL_FIELD_NAMES =
+ List.of("isDataDeletable", "isDataEncrypted");
+
+ private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml";
+
+ private Document mDoc = null;
+
+ /** Logic for setting up tests (empty if not yet needed). */
+ public static void main(String[] params) throws Exception {}
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for all fields valid. */
+ @Test
+ public void testAllFieldsValid() throws Exception {
+ System.out.println("starting testAllFieldsValid.");
+ testHrToOdSecurityLabels(ALL_FIELDS_VALID_FILE_NAME);
+ }
+
+ /** Tests missing optional fields passes. */
+ @Test
+ public void testMissingOptionalFields() throws Exception {
+ for (String optField : OPTIONAL_FIELD_NAMES) {
+ var ele =
+ TestUtils.getElementsFromResource(
+ Paths.get(SECURITY_LABELS_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ ele.get(0).removeAttribute(optField);
+ SecurityLabels securityLabels = new SecurityLabelsFactory().createFromHrElements(ele);
+ securityLabels.toOdDomElements(mDoc);
+ }
+ }
+
+ private void testHrToOdSecurityLabels(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc,
+ new SecurityLabelsFactory(),
+ SECURITY_LABELS_HR_PATH,
+ SECURITY_LABELS_OD_PATH,
+ fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java
new file mode 100644
index 000000000000..ab8e85cd022b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib.marshallable;
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+@RunWith(JUnit4.class)
+public class ThirdPartyVerificationTest {
+ private static final String THIRD_PARTY_VERIFICATION_HR_PATH =
+ "com/android/asllib/thirdpartyverification/hr";
+ private static final String THIRD_PARTY_VERIFICATION_OD_PATH =
+ "com/android/asllib/thirdpartyverification/od";
+
+ private static final String VALID_FILE_NAME = "valid.xml";
+ private static final String MISSING_URL_FILE_NAME = "missing-url.xml";
+
+ private Document mDoc = null;
+
+ /** Logic for setting up tests (empty if not yet needed). */
+ public static void main(String[] params) throws Exception {}
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for valid. */
+ @Test
+ public void testValid() throws Exception {
+ System.out.println("starting testValid.");
+ testHrToOdThirdPartyVerification(VALID_FILE_NAME);
+ }
+
+ /** Tests missing url. */
+ @Test
+ public void testMissingUrl() throws Exception {
+ System.out.println("starting testMissingUrl.");
+ hrToOdExpectException(MISSING_URL_FILE_NAME);
+ }
+
+ private void hrToOdExpectException(String fileName) {
+ TestUtils.hrToOdExpectException(
+ new ThirdPartyVerificationFactory(), THIRD_PARTY_VERIFICATION_HR_PATH, fileName);
+ }
+
+ private void testHrToOdThirdPartyVerification(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc,
+ new ThirdPartyVerificationFactory(),
+ THIRD_PARTY_VERIFICATION_HR_PATH,
+ THIRD_PARTY_VERIFICATION_OD_PATH,
+ fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
index faea340ae7bd..6a29b869be43 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
@@ -26,6 +26,8 @@ import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
@@ -72,7 +74,7 @@ public class TestUtils {
.findFirst()
.get()
.getTagName();
- return XmlUtils.asElementList(root.getElementsByTagName(tagName));
+ return XmlUtils.getChildrenByTagName(root, tagName);
} else {
return List.of(root);
}
@@ -105,7 +107,7 @@ public class TestUtils {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
Document document = factory.newDocumentBuilder().parse(stream);
-
+ stripEmptyElements(document);
return docToStr(document, omitXmlDeclaration);
}
@@ -125,6 +127,17 @@ public class TestUtils {
});
}
+ /** Helper for testing on-device to human-readable conversion expecting exception */
+ public static <T extends AslMarshallable> void odToHrExpectException(
+ AslMarshallableFactory<T> factory, String odFolderPath, String fileName) {
+ assertThrows(
+ MalformedXmlException.class,
+ () -> {
+ factory.createFromOdElements(
+ TestUtils.getElementsFromResource(Paths.get(odFolderPath, fileName)));
+ });
+ }
+
/** Helper for testing human-readable to on-device conversion */
public static <T extends AslMarshallable> void testHrToOd(
Document doc,
@@ -133,20 +146,71 @@ public class TestUtils {
String odFolderPath,
String fileName)
throws Exception {
+ testFormatToFormat(doc, factory, hrFolderPath, odFolderPath, fileName, true);
+ }
+
+ /** Helper for testing on-device to human-readable conversion */
+ public static <T extends AslMarshallable> void testOdToHr(
+ Document doc,
+ AslMarshallableFactory<T> factory,
+ String odFolderPath,
+ String hrFolderPath,
+ String fileName)
+ throws Exception {
+ testFormatToFormat(doc, factory, odFolderPath, hrFolderPath, fileName, false);
+ }
+
+ /** Helper for testing format to format conversion */
+ private static <T extends AslMarshallable> void testFormatToFormat(
+ Document doc,
+ AslMarshallableFactory<T> factory,
+ String inFolderPath,
+ String outFolderPath,
+ String fileName,
+ boolean hrToOd)
+ throws Exception {
AslMarshallable marshallable =
- factory.createFromHrElements(
- TestUtils.getElementsFromResource(Paths.get(hrFolderPath, fileName)));
+ hrToOd
+ ? factory.createFromHrElements(
+ TestUtils.getElementsFromResource(
+ Paths.get(inFolderPath, fileName)))
+ : factory.createFromOdElements(
+ TestUtils.getElementsFromResource(
+ Paths.get(inFolderPath, fileName)));
+
+ List<Element> elements =
+ hrToOd ? marshallable.toOdDomElements(doc) : marshallable.toHrDomElements(doc);
+ if (elements.isEmpty()) {
+ throw new IllegalStateException("elements was empty.");
+ } else if (elements.size() == 1) {
+ doc.appendChild(elements.get(0));
+ } else {
+ Element root = doc.createElement(TestUtils.HOLDER_TAG_NAME);
+ for (var child : elements) {
+ root.appendChild(child);
+ }
+ doc.appendChild(root);
+ }
+ String converted = TestUtils.getFormattedXml(TestUtils.docToStr(doc, true), true);
+ System.out.println("Converted: " + converted);
+ String expectedOutContents =
+ TestUtils.getFormattedXml(
+ TestUtils.readStrFromResource(Paths.get(outFolderPath, fileName)), true);
+ System.out.println("Expected: " + expectedOutContents);
+ assertEquals(expectedOutContents, converted);
+ }
- for (var child : marshallable.toOdDomElements(doc)) {
- doc.appendChild(child);
+ private static void stripEmptyElements(Node node) {
+ NodeList children = node.getChildNodes();
+ for (int i = 0; i < children.getLength(); ++i) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.TEXT_NODE) {
+ if (child.getTextContent().trim().length() == 0) {
+ child.getParentNode().removeChild(child);
+ i--;
+ }
+ }
+ stripEmptyElements(child);
}
- String converted = TestUtils.docToStr(doc, true);
- System.out.println("converted: " + converted);
-
- String expectedOdContents =
- TestUtils.readStrFromResource(Paths.get(odFolderPath, fileName));
- assertEquals(
- TestUtils.getFormattedXml(expectedOdContents, true),
- TestUtils.getFormattedXml(converted, true));
}
}
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml
new file mode 100644
index 000000000000..68e191e8ebe3
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml
@@ -0,0 +1,17 @@
+<data-labels>
+ <data-shared dataCategory="actions_in_app"
+ dataType="user_interaction"
+ purposes="analytics" />
+ <data-shared dataCategory="actions_in_app"
+ dataType="in_app_search_history"
+ purposes="analytics" />
+ <data-shared dataCategory="actions_in_app"
+ dataType="installed_apps"
+ purposes="analytics" />
+ <data-shared dataCategory="actions_in_app"
+ dataType="user_generated_content"
+ purposes="analytics" />
+ <data-shared dataCategory="actions_in_app"
+ dataType="other"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml
new file mode 100644
index 000000000000..a6bd17d63704
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml
@@ -0,0 +1,11 @@
+<data-labels>
+ <data-shared dataCategory="app_performance"
+ dataType="crash_logs"
+ purposes="analytics" />
+ <data-shared dataCategory="app_performance"
+ dataType="performance_diagnostics"
+ purposes="analytics" />
+ <data-shared dataCategory="app_performance"
+ dataType="other"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml
new file mode 100644
index 000000000000..6274604f3431
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml
@@ -0,0 +1,11 @@
+<data-labels>
+ <data-shared dataCategory="audio"
+ dataType="sound_recordings"
+ purposes="analytics" />
+ <data-shared dataCategory="audio"
+ dataType="music_files"
+ purposes="analytics" />
+ <data-shared dataCategory="audio"
+ dataType="other"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml
new file mode 100644
index 000000000000..f7201f625629
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml
@@ -0,0 +1,5 @@
+<data-labels>
+ <data-shared dataCategory="calendar"
+ dataType="calendar"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml
new file mode 100644
index 000000000000..e8d40be91d90
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml
@@ -0,0 +1,5 @@
+<data-labels>
+ <data-shared dataCategory="contacts"
+ dataType="contacts"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml
new file mode 100644
index 000000000000..69e9b87db287
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml
@@ -0,0 +1,11 @@
+<data-labels>
+ <data-shared dataCategory="email_text_message"
+ dataType="emails"
+ purposes="analytics" />
+ <data-shared dataCategory="email_text_message"
+ dataType="text_messages"
+ purposes="analytics" />
+ <data-shared dataCategory="email_text_message"
+ dataType="other"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml
new file mode 100644
index 000000000000..fdd84569a5df
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml
@@ -0,0 +1,14 @@
+<data-labels>
+ <data-shared dataCategory="financial"
+ dataType="card_bank_account"
+ purposes="analytics" />
+ <data-shared dataCategory="financial"
+ dataType="purchase_history"
+ purposes="analytics" />
+ <data-shared dataCategory="financial"
+ dataType="credit_score"
+ purposes="analytics" />
+ <data-shared dataCategory="financial"
+ dataType="other"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml
new file mode 100644
index 000000000000..bac58e6fb7df
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml
@@ -0,0 +1,8 @@
+<data-labels>
+ <data-shared dataCategory="health_fitness"
+ dataType="health"
+ purposes="analytics" />
+ <data-shared dataCategory="health_fitness"
+ dataType="fitness"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml
new file mode 100644
index 000000000000..ee45f269eb7f
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml
@@ -0,0 +1,5 @@
+<data-labels>
+ <data-shared dataCategory="identifiers"
+ dataType="other"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml
new file mode 100644
index 000000000000..e8e59118f69c
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml
@@ -0,0 +1,8 @@
+<data-labels>
+ <data-shared dataCategory="location"
+ dataType="approx_location"
+ purposes="analytics" />
+ <data-shared dataCategory="location"
+ dataType="precise_location"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml
new file mode 100644
index 000000000000..0b220f43e5af
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml
@@ -0,0 +1,5 @@
+<data-labels>
+ <data-shared dataCategory="personal"
+ dataType="email_address"
+ purposes="" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml
new file mode 100644
index 000000000000..ac221f208577
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml
@@ -0,0 +1,4 @@
+<data-labels>
+ <data-shared dataCategory="personal"
+ dataType="email_address" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml
new file mode 100644
index 000000000000..11b73689230b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml
@@ -0,0 +1,8 @@
+<data-labels>
+ <data-shared dataCategory="personal"
+ dataType="name"
+ purposes="analytics|developer_communications" />
+ <data-shared dataCategory="personal"
+ dataType="email_address"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml
new file mode 100644
index 000000000000..f1fbd56571b2
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml
@@ -0,0 +1,5 @@
+<data-labels>
+ <data-shared dataCategory="personal"
+ dataType="unrecognized"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml
new file mode 100644
index 000000000000..59074628de70
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml
@@ -0,0 +1,31 @@
+<data-labels>
+ <data-shared dataCategory="personal"
+ dataType="name"
+ ephemeral="true"
+ isSharingOptional="true"
+ purposes="analytics|developer_communications" />
+ <data-shared dataCategory="personal"
+ dataType="email_address"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="physical_address"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="phone_number"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="race_ethnicity"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="political_or_religious_beliefs"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="sexual_orientation_or_gender_identity"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="personal_identifiers"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="other"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml
new file mode 100644
index 000000000000..05fe159a1d7c
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml
@@ -0,0 +1,8 @@
+<data-labels>
+ <data-shared dataCategory="photo_video"
+ dataType="photos"
+ purposes="analytics" />
+ <data-shared dataCategory="photo_video"
+ dataType="videos"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml
new file mode 100644
index 000000000000..a5de7bed4152
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml
@@ -0,0 +1,5 @@
+<data-labels>
+ <data-shared dataCategory="search_and_browsing"
+ dataType="web_browsing_history"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml
new file mode 100644
index 000000000000..f01e2df8d99a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml
@@ -0,0 +1,5 @@
+<data-labels>
+ <data-shared dataCategory="storage"
+ dataType="files_docs"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml
new file mode 100644
index 000000000000..f1fbd56571b2
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml
@@ -0,0 +1,5 @@
+<data-labels>
+ <data-shared dataCategory="personal"
+ dataType="unrecognized"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml
new file mode 100644
index 000000000000..c5be68424226
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml
@@ -0,0 +1,5 @@
+<data-labels>
+ <data-shared dataCategory="unrecognized"
+ dataType="email_address"
+ purposes="analytics" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml
new file mode 100644
index 000000000000..161057a51b79
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml
@@ -0,0 +1,11 @@
+<data-labels>
+ <data-accessed dataCategory="location"
+ dataType="approx_location"
+ purposes="app_functionality" />
+ <data-collected dataCategory="location"
+ dataType="precise_location"
+ purposes="app_functionality" />
+ <data-shared dataCategory="personal"
+ dataType="name"
+ purposes="app_functionality" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-actions-in-app.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-actions-in-app.xml
new file mode 100644
index 000000000000..c5fef58cb25c
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-actions-in-app.xml
@@ -0,0 +1,31 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="actions_in_app">
+ <pbundle_as_map name="user_interaction">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="in_app_search_history">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="installed_apps">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="user_generated_content">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-app-performance.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-app-performance.xml
new file mode 100644
index 000000000000..4570145a0436
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-app-performance.xml
@@ -0,0 +1,21 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="app_performance">
+ <pbundle_as_map name="crash_logs">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="performance_diagnostics">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-audio.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-audio.xml
new file mode 100644
index 000000000000..120f626549f7
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-audio.xml
@@ -0,0 +1,21 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="audio">
+ <pbundle_as_map name="sound_recordings">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="music_files">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-calendar.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-calendar.xml
new file mode 100644
index 000000000000..59eb93812690
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-calendar.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="calendar">
+ <pbundle_as_map name="calendar">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-contacts.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-contacts.xml
new file mode 100644
index 000000000000..f952bfb4df79
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-contacts.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="contacts">
+ <pbundle_as_map name="contacts">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-email-text-message.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-email-text-message.xml
new file mode 100644
index 000000000000..bcaa716559d4
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-email-text-message.xml
@@ -0,0 +1,21 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="email_text_message">
+ <pbundle_as_map name="emails">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="text_messages">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-financial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-financial.xml
new file mode 100644
index 000000000000..a7bc82e38dac
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-financial.xml
@@ -0,0 +1,26 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="financial">
+ <pbundle_as_map name="card_bank_account">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="purchase_history">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="credit_score">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-health-fitness.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-health-fitness.xml
new file mode 100644
index 000000000000..f3ab2bd90733
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-health-fitness.xml
@@ -0,0 +1,16 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="health_fitness">
+ <pbundle_as_map name="health">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="fitness">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-identifiers.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-identifiers.xml
new file mode 100644
index 000000000000..05c07e54c5a8
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-identifiers.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="identifiers">
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-location.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-location.xml
new file mode 100644
index 000000000000..931d1adc0218
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-location.xml
@@ -0,0 +1,16 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="precise_location">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-empty-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-empty-purpose.xml
new file mode 100644
index 000000000000..83f4a67cd54d
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-empty-purpose.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="personal">
+ <pbundle_as_map name="email_address">
+
+ <int-array name="purposes" num="2">
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-missing-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-missing-purpose.xml
new file mode 100644
index 000000000000..532f5deee655
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-missing-purpose.xml
@@ -0,0 +1,8 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="personal">
+ <pbundle_as_map name="email_address">
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml
new file mode 100644
index 000000000000..14f9ef2b74ad
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml
@@ -0,0 +1,17 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="personal">
+ <pbundle_as_map name="name">
+ <int-array name="purposes" num="2">
+ <item value="2" />
+ <item value="3" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="email_address">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml
new file mode 100644
index 000000000000..1c87de9e67a7
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml
@@ -0,0 +1,54 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="personal">
+ <pbundle_as_map name="name">
+ <int-array name="purposes" num="2">
+ <item value="2" />
+ <item value="3" />
+ </int-array>
+ <boolean name="is_sharing_optional" value="true" />
+ <boolean name="ephemeral" value="true" />
+ </pbundle_as_map>
+ <pbundle_as_map name="email_address">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="physical_address">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="phone_number">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="race_ethnicity">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="political_or_religious_beliefs">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="sexual_orientation_or_gender_identity">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="personal_identifiers">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-photo-video.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-photo-video.xml
new file mode 100644
index 000000000000..a752b5152c25
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-photo-video.xml
@@ -0,0 +1,16 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="photo_video">
+ <pbundle_as_map name="photos">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="videos">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-search-and-browsing.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-search-and-browsing.xml
new file mode 100644
index 000000000000..99bc1881b4f1
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-search-and-browsing.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="search_and_browsing">
+ <pbundle_as_map name="web_browsing_history">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-storage.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-storage.xml
new file mode 100644
index 000000000000..a4d2a6249119
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-storage.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="storage">
+ <pbundle_as_map name="files_docs">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized-type.xml
new file mode 100644
index 000000000000..16195df53b3a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized-type.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="personal">
+ <pbundle_as_map name="unrecognized">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized.xml
new file mode 100644
index 000000000000..511940eafd4a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="unrecognized">
+ <pbundle_as_map name="email_address">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-collected-shared.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-collected-shared.xml
new file mode 100644
index 000000000000..f86dbc1a63b2
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-collected-shared.xml
@@ -0,0 +1,29 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_accessed">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ <pbundle_as_map name="data_collected">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="precise_location">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="personal">
+ <pbundle_as_map name="name">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-invalid-bool.xml
new file mode 100644
index 000000000000..54cc8e7049b2
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-invalid-bool.xml
@@ -0,0 +1,13 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_collected">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ <boolean name="is_sharing_optional" value="false"/>
+ <boolean name="ephemeral" value="false"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml
index d1d4e33e855a..3864f986d20d 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml
@@ -1,5 +1,5 @@
<pbundle_as_map name="data_labels">
- <pbundle_as_map name="data_shared">
+<pbundle_as_map name="data_shared">
<pbundle_as_map name="location">
<pbundle_as_map name="approx_location">
<int-array name="purposes" num="1">
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml
new file mode 100644
index 000000000000..940e48a68ce8
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml
@@ -0,0 +1,6 @@
+<safety-labels version="12345">
+ <security-labels
+ isDataDeletable="true"
+ isDataEncrypted="false"
+ />
+</safety-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml
new file mode 100644
index 000000000000..bfbc5ae70974
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml
@@ -0,0 +1,4 @@
+<safety-labels version="12345">
+<third-party-verification url="www.example.com">
+ </third-party-verification>
+</safety-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml
new file mode 100644
index 000000000000..b39c562b30d0
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml
@@ -0,0 +1,7 @@
+<pbundle_as_map name="safety_labels">
+ <long name="version" value="12345"/>
+ <pbundle_as_map name="security_labels">
+ <boolean name="is_data_deletable" value="true" />
+ <boolean name="is_data_encrypted" value="false" />
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml
new file mode 100644
index 000000000000..10653ff5027b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml
@@ -0,0 +1,6 @@
+<pbundle_as_map name="safety_labels">
+ <long name="version" value="12345"/>
+ <pbundle_as_map name="third_party_verification">
+ <string name="url" value="www.example.com"/>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/securitylabels/hr/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/securitylabels/hr/all-fields-valid.xml
new file mode 100644
index 000000000000..e2fb592cba07
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/securitylabels/hr/all-fields-valid.xml
@@ -0,0 +1,4 @@
+<security-labels
+ isDataDeletable="true"
+ isDataEncrypted="false">
+</security-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/securitylabels/od/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/securitylabels/od/all-fields-valid.xml
new file mode 100644
index 000000000000..7b2f656ec832
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/securitylabels/od/all-fields-valid.xml
@@ -0,0 +1,4 @@
+<pbundle_as_map name="security_labels">
+ <boolean name="is_data_deletable" value="true" />
+ <boolean name="is_data_encrypted" value="false" />
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/hr/missing-url.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/hr/missing-url.xml
new file mode 100644
index 000000000000..6738ac2f2446
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/hr/missing-url.xml
@@ -0,0 +1 @@
+<third-party-verification></third-party-verification> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/hr/valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/hr/valid.xml
new file mode 100644
index 000000000000..2a664f22b916
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/hr/valid.xml
@@ -0,0 +1 @@
+<third-party-verification url="www.example.com"></third-party-verification> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/od/valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/od/valid.xml
new file mode 100644
index 000000000000..dbeb592d7e88
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/od/valid.xml
@@ -0,0 +1,3 @@
+<pbundle_as_map name="third_party_verification">
+ <string name="url" value="www.example.com"/>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
new file mode 100644
index 000000000000..36beb93319cd
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
@@ -0,0 +1,35 @@
+<app-metadata-bundles version="123">
+ <system-app-safety-label url="www.example.com">
+ </system-app-safety-label>
+ <safety-labels version="12345">
+ <data-labels>
+ <data-shared dataCategory="location"
+ dataType="approx_location"
+ isSharingOptional="false"
+ ephemeral="false"
+ purposes="app_functionality" />
+ <data-shared dataCategory="location"
+ dataType="precise_location"
+ isSharingOptional="true"
+ ephemeral="true"
+ purposes="app_functionality|analytics" />
+ </data-labels>
+ <security-labels
+ isDataDeletable="true"
+ isDataEncrypted="false"
+ />
+ <third-party-verification url="www.example.com">
+ </third-party-verification>
+ </safety-labels>
+ <transparency-info>
+ <developer-info
+ name="max"
+ email="max@example.com"
+ address="111 blah lane"
+ countryRegion="US"
+ relationship="aosp"
+ website="example.com"
+ appDeveloperRegistryId="registry_id" />
+ <app-info title="beervision" description="a beer app" containsAds="true" obeyAps="false" adsFingerprinting="false" securityFingerprinting="false" privacyPolicy="www.example.com" securityEndpoints="url1|url2|url3" firstPartyEndpoints="url1" serviceProviderEndpoints="url55|url56" category="Food and drink" email="max@maxloh.com" />
+ </transparency-info>
+</app-metadata-bundles> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
new file mode 100644
index 000000000000..db21280ad61b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
@@ -0,0 +1,70 @@
+<bundle>
+ <long name="version" value="123"/>
+ <pbundle_as_map name="safety_labels">
+ <long name="version" value="12345"/>
+ <pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ <boolean name="is_sharing_optional" value="false"/>
+ <boolean name="ephemeral" value="false"/>
+ </pbundle_as_map>
+ <pbundle_as_map name="precise_location">
+ <int-array name="purposes" num="2">
+ <item value="1"/>
+ <item value="2"/>
+ </int-array>
+ <boolean name="is_sharing_optional" value="true"/>
+ <boolean name="ephemeral" value="true"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ <pbundle_as_map name="security_labels">
+ <boolean name="is_data_deletable" value="true"/>
+ <boolean name="is_data_encrypted" value="false"/>
+ </pbundle_as_map>
+ <pbundle_as_map name="third_party_verification">
+ <string name="url" value="www.example.com"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ <pbundle_as_map name="system_app_safety_label">
+ <string name="url" value="www.example.com"/>
+ </pbundle_as_map>
+ <pbundle_as_map name="transparency_info">
+ <pbundle_as_map name="developer_info">
+ <string name="name" value="max"/>
+ <string name="email" value="max@example.com"/>
+ <string name="address" value="111 blah lane"/>
+ <string name="country_region" value="US"/>
+ <long name="relationship" value="5"/>
+ <string name="website" value="example.com"/>
+ </pbundle_as_map>
+ <pbundle_as_map name="app_info">
+ <string name="title" value="beervision"/>
+ <string name="description" value="a beer app"/>
+ <boolean name="contains_ads" value="true"/>
+ <boolean name="obey_aps" value="false"/>
+ <boolean name="ads_fingerprinting" value="false"/>
+ <boolean name="security_fingerprinting" value="false"/>
+ <string name="privacy_policy" value="www.example.com"/>
+ <string-array name="security_endpoint" num="3">
+ <item value="url1"/>
+ <item value="url2"/>
+ <item value="url3"/>
+ </string-array>
+ <string-array name="first_party_endpoint" num="1">
+ <item value="url1"/>
+ </string-array>
+ <string-array name="service_provider_endpoint" num="2">
+ <item value="url55"/>
+ <item value="url56"/>
+ </string-array>
+ <string name="category" value="Food and drink"/>
+ <string name="email" value="max@maxloh.com"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</bundle> \ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 30333da5e86c..682adbc86d06 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -82,13 +82,30 @@ java_library {
jarjar_rules: "jarjar-rules.txt",
}
+// For sharing the code with other tools
+java_library_host {
+ name: "hoststubgen-lib",
+ defaults: ["ravenwood-internal-only-visibility-java"],
+ srcs: ["src/**/*.kt"],
+ static_libs: [
+ "hoststubgen-helper-runtime",
+ ],
+ libs: [
+ "junit",
+ "ow2-asm",
+ "ow2-asm-analysis",
+ "ow2-asm-commons",
+ "ow2-asm-tree",
+ "ow2-asm-util",
+ ],
+}
+
// Host-side stub generator tool.
java_binary_host {
name: "hoststubgen",
- main_class: "com.android.hoststubgen.Main",
- srcs: ["src/**/*.kt"],
+ main_class: "com.android.hoststubgen.HostStubGenMain",
static_libs: [
- "hoststubgen-helper-runtime",
+ "hoststubgen-lib",
"junit",
"ow2-asm",
"ow2-asm-analysis",
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 1089f82b6472..803dc283b8c7 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -32,7 +32,6 @@ import com.android.hoststubgen.visitors.PackageRedirectRemapper
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
-import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.util.CheckClassAdapter
import java.io.BufferedInputStream
import java.io.FileOutputStream
@@ -52,7 +51,7 @@ class HostStubGen(val options: HostStubGenOptions) {
val stats = HostStubGenStats()
// Load all classes.
- val allClasses = loadClassStructures(options.inJar.get)
+ val allClasses = ClassNodes.loadClassStructures(options.inJar.get)
// Dump the classes, if specified.
options.inputJarDumpFile.ifSet {
@@ -92,55 +91,6 @@ class HostStubGen(val options: HostStubGenOptions) {
}
/**
- * Load all the classes, without code.
- */
- private fun loadClassStructures(inJar: String): ClassNodes {
- log.i("Reading class structure from $inJar ...")
- val start = System.currentTimeMillis()
-
- val allClasses = ClassNodes()
-
- log.withIndent {
- ZipFile(inJar).use { inZip ->
- val inEntries = inZip.entries()
-
- while (inEntries.hasMoreElements()) {
- val entry = inEntries.nextElement()
-
- BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
- if (entry.name.endsWith(".class")) {
- val cr = ClassReader(bis)
- val cn = ClassNode()
- cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG
- or ClassReader.SKIP_FRAMES)
- if (!allClasses.addClass(cn)) {
- log.w("Duplicate class found: ${cn.name}")
- }
- } else if (entry.name.endsWith(".dex")) {
- // Seems like it's an ART jar file. We can't process it.
- // It's a fatal error.
- throw InvalidJarFileException(
- "$inJar is not a desktop jar file. It contains a *.dex file.")
- } else {
- // Unknown file type. Skip.
- while (bis.available() > 0) {
- bis.skip((1024 * 1024).toLong())
- }
- }
- }
- }
- }
- }
- if (allClasses.size == 0) {
- log.w("$inJar contains no *.class files.")
- }
-
- val end = System.currentTimeMillis()
- log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
- return allClasses
- }
-
- /**
* Build the filter, which decides what classes/methods/fields should be put in stub or impl
* jars, and "how". (e.g. with substitution?)
*/
@@ -229,7 +179,7 @@ class HostStubGen(val options: HostStubGenOptions) {
val intersectingJars = mutableMapOf<String, ClassNodes>()
filenames.forEach { filename ->
- intersectingJars[filename] = loadClassStructures(filename)
+ intersectingJars[filename] = ClassNodes.loadClassStructures(filename)
}
return intersectingJars
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
index 4882c00d2b3c..45e7e301c0d1 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
@@ -13,18 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@file:JvmName("Main")
+@file:JvmName("HostStubGenMain")
package com.android.hoststubgen
import java.io.PrintWriter
-const val COMMAND_NAME = "HostStubGen"
-
/**
* Entry point.
*/
fun main(args: Array<String>) {
+ executableName = "HostStubGen"
+
var success = false
var clanupOnError = false
@@ -33,7 +33,7 @@ fun main(args: Array<String>) {
val options = HostStubGenOptions.parseArgs(args)
clanupOnError = options.cleanUpOnError.get
- log.v("HostStubGen started")
+ log.v("$executableName started")
log.v("Options: $options")
// Run.
@@ -41,7 +41,7 @@ fun main(args: Array<String>) {
success = true
} catch (e: Throwable) {
- log.e("$COMMAND_NAME: Error: ${e.message}")
+ log.e("$executableName: Error: ${e.message}")
if (e !is UserErrorException) {
e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error)))
}
@@ -49,7 +49,7 @@ fun main(args: Array<String>) {
TODO("Remove output jars here")
}
} finally {
- log.i("$COMMAND_NAME finished")
+ log.i("$executableName finished")
log.flush()
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 9f5d524517d0..9ff798a4b5cb 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -268,7 +268,7 @@ class HostStubGenOptions(
}
if (!ret.outStubJar.isSet && !ret.outImplJar.isSet) {
log.w("Neither --out-stub-jar nor --out-impl-jar is set." +
- " $COMMAND_NAME will not generate jar files.")
+ " $executableName will not generate jar files.")
}
if (ret.enableNonStubMethodCallDetection.get) {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
index 937e56c2cbb5..aa63d8d9f870 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
@@ -16,6 +16,11 @@
package com.android.hoststubgen
/**
+ * Name of this executable. Set it in the main method.
+ */
+var executableName = "[command name not set]"
+
+/**
* A regex that maches whitespate.
*/
val whitespaceRegex = """\s+""".toRegex()
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index 0579c2bb0525..83e122feeeb2 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -34,6 +34,9 @@ val CLASS_INITIALIZER_NAME = "<clinit>"
/** Descriptor of the class initializer method. */
val CLASS_INITIALIZER_DESC = "()V"
+/** Name of constructors. */
+val CTOR_NAME = "<init>"
+
/**
* Find any of [anyAnnotations] from the list of visible / invisible annotations.
*/
@@ -135,10 +138,10 @@ fun writeByteCodeToPushArguments(
// Note, long and double will consume two local variable spaces, so the extra `i++`.
when (type) {
Type.VOID_TYPE -> throw HostStubGenInternalException("VOID_TYPE not expected")
- Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.SHORT_TYPE, Type.CHAR_TYPE
+ Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE
-> writer.visitVarInsn(Opcodes.ILOAD, i)
- Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++)
Type.FLOAT_TYPE -> writer.visitVarInsn(Opcodes.FLOAD, i)
+ Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++)
Type.DOUBLE_TYPE -> writer.visitVarInsn(Opcodes.DLOAD, i++)
else -> writer.visitVarInsn(Opcodes.ALOAD, i)
}
@@ -154,10 +157,10 @@ fun writeByteCodeToReturn(methodDescriptor: String, writer: MethodVisitor) {
// See https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions
when (type) {
Type.VOID_TYPE -> writer.visitInsn(Opcodes.RETURN)
- Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.SHORT_TYPE, Type.CHAR_TYPE
+ Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE
-> writer.visitInsn(Opcodes.IRETURN)
- Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN)
Type.FLOAT_TYPE -> writer.visitInsn(Opcodes.FRETURN)
+ Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN)
Type.DOUBLE_TYPE -> writer.visitInsn(Opcodes.DRETURN)
else -> writer.visitInsn(Opcodes.ARETURN)
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
index bc34ef0dc8a7..92906a75b93a 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
@@ -16,13 +16,18 @@
package com.android.hoststubgen.asm
import com.android.hoststubgen.ClassParseException
+import com.android.hoststubgen.InvalidJarFileException
+import com.android.hoststubgen.log
+import org.objectweb.asm.ClassReader
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.MethodNode
import org.objectweb.asm.tree.TypeAnnotationNode
+import java.io.BufferedInputStream
import java.io.PrintWriter
import java.util.Arrays
+import java.util.zip.ZipFile
/**
* Stores all classes loaded from a jar file, in a form of [ClassNode]
@@ -62,8 +67,8 @@ class ClassNodes {
/** Find a field, which may not exist. */
fun findField(
- className: String,
- fieldName: String,
+ className: String,
+ fieldName: String,
): FieldNode? {
return findClass(className)?.fields?.firstOrNull { it.name == fieldName }?.let { fn ->
return fn
@@ -72,14 +77,14 @@ class ClassNodes {
/** Find a method, which may not exist. */
fun findMethod(
- className: String,
- methodName: String,
- descriptor: String,
+ className: String,
+ methodName: String,
+ descriptor: String,
): MethodNode? {
return findClass(className)?.methods
- ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
- return mn
- }
+ ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
+ return mn
+ }
}
/** @return true if a class has a class initializer. */
@@ -106,26 +111,33 @@ class ClassNodes {
private fun dumpClass(pw: PrintWriter, cn: ClassNode) {
pw.printf("Class: %s [access: %x]\n", cn.name, cn.access)
- dumpAnnotations(pw, " ",
- cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations,
- cn.visibleAnnotations, cn.invisibleAnnotations,
- )
+ dumpAnnotations(
+ pw, " ",
+ cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations,
+ cn.visibleAnnotations, cn.invisibleAnnotations,
+ )
for (f in cn.fields ?: emptyList()) {
- pw.printf(" Field: %s [sig: %s] [desc: %s] [access: %x]\n",
- f.name, f.signature, f.desc, f.access)
- dumpAnnotations(pw, " ",
- f.visibleTypeAnnotations, f.invisibleTypeAnnotations,
- f.visibleAnnotations, f.invisibleAnnotations,
- )
+ pw.printf(
+ " Field: %s [sig: %s] [desc: %s] [access: %x]\n",
+ f.name, f.signature, f.desc, f.access
+ )
+ dumpAnnotations(
+ pw, " ",
+ f.visibleTypeAnnotations, f.invisibleTypeAnnotations,
+ f.visibleAnnotations, f.invisibleAnnotations,
+ )
}
for (m in cn.methods ?: emptyList()) {
- pw.printf(" Method: %s [sig: %s] [desc: %s] [access: %x]\n",
- m.name, m.signature, m.desc, m.access)
- dumpAnnotations(pw, " ",
- m.visibleTypeAnnotations, m.invisibleTypeAnnotations,
- m.visibleAnnotations, m.invisibleAnnotations,
- )
+ pw.printf(
+ " Method: %s [sig: %s] [desc: %s] [access: %x]\n",
+ m.name, m.signature, m.desc, m.access
+ )
+ dumpAnnotations(
+ pw, " ",
+ m.visibleTypeAnnotations, m.invisibleTypeAnnotations,
+ m.visibleAnnotations, m.invisibleAnnotations,
+ )
}
}
@@ -136,7 +148,7 @@ class ClassNodes {
invisibleTypeAnnotations: List<TypeAnnotationNode>?,
visibleAnnotations: List<AnnotationNode>?,
invisibleAnnotations: List<AnnotationNode>?,
- ) {
+ ) {
for (an in visibleTypeAnnotations ?: emptyList()) {
pw.printf("%sTypeAnnotation(vis): %s\n", prefix, an.desc)
}
@@ -166,4 +178,55 @@ class ClassNodes {
}
}
}
+
+ companion object {
+ /**
+ * Load all the classes, without code.
+ */
+ fun loadClassStructures(inJar: String): ClassNodes {
+ log.i("Reading class structure from $inJar ...")
+ val start = System.currentTimeMillis()
+
+ val allClasses = ClassNodes()
+
+ log.withIndent {
+ ZipFile(inJar).use { inZip ->
+ val inEntries = inZip.entries()
+
+ while (inEntries.hasMoreElements()) {
+ val entry = inEntries.nextElement()
+
+ BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+ if (entry.name.endsWith(".class")) {
+ val cr = ClassReader(bis)
+ val cn = ClassNode()
+ cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG
+ or ClassReader.SKIP_FRAMES)
+ if (!allClasses.addClass(cn)) {
+ log.w("Duplicate class found: ${cn.name}")
+ }
+ } else if (entry.name.endsWith(".dex")) {
+ // Seems like it's an ART jar file. We can't process it.
+ // It's a fatal error.
+ throw InvalidJarFileException(
+ "$inJar is not a desktop jar file. It contains a *.dex file.")
+ } else {
+ // Unknown file type. Skip.
+ while (bis.available() > 0) {
+ bis.skip((1024 * 1024).toLong())
+ }
+ }
+ }
+ }
+ }
+ }
+ if (allClasses.size == 0) {
+ log.w("$inJar contains no *.class files.")
+ }
+
+ val end = System.currentTimeMillis()
+ log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
+ return allClasses
+ }
+ }
} \ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
index 78b13fd36f06..5a26fc69d473 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
@@ -19,14 +19,14 @@ import com.android.hoststubgen.HostStubGenErrors
import com.android.hoststubgen.HostStubGenInternalException
import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
-import com.android.hoststubgen.asm.isAnonymousInnerClass
-import com.android.hoststubgen.log
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.isAnnotation
+import com.android.hoststubgen.asm.isAnonymousInnerClass
import com.android.hoststubgen.asm.isAutoGeneratedEnumMember
import com.android.hoststubgen.asm.isEnum
import com.android.hoststubgen.asm.isSynthetic
import com.android.hoststubgen.asm.isVisibilityPrivateOrPackagePrivate
+import com.android.hoststubgen.log
import org.objectweb.asm.tree.ClassNode
/**
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index f70a17d9b7cd..fa8fe6cd384f 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -1833,7 +1833,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 10, attributes: 2
+ interfaces: 0, fields: 1, methods: 11, attributes: 2
int value;
descriptor: I
flags: (0x0000)
@@ -1938,6 +1938,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V
x: athrow
LineNumberTable:
+
+ public static native byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
}
SourceFile: "TinyFrameworkNative.java"
RuntimeInvisibleAnnotations:
@@ -1955,7 +1959,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 4, attributes: 2
+ interfaces: 0, fields: 0, methods: 5, attributes: 2
public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2013,6 +2017,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
Start Length Slot Name Signature
0 7 0 source Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
0 7 1 arg I
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_0
+ x: iload_1
+ x: iadd
+ x: i2b
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 arg1 B
+ 0 5 1 arg2 B
}
SourceFile: "TinyFrameworkNative_host.java"
RuntimeInvisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index 37de857b9780..c605f767f527 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -1554,7 +1554,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 9, attributes: 3
+ interfaces: 0, fields: 1, methods: 10, attributes: 3
int value;
descriptor: I
flags: (0x0000)
@@ -1686,6 +1686,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static native byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative.java"
RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index c9c607c58c68..11d5939b7917 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -2236,7 +2236,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 10, attributes: 3
+ interfaces: 0, fields: 1, methods: 11, attributes: 3
int value;
descriptor: I
flags: (0x0000)
@@ -2435,6 +2435,23 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_0
+ x: iload_1
+ x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B
+ x: ireturn
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative.java"
RuntimeVisibleAnnotations:
@@ -2457,7 +2474,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 4, attributes: 3
+ interfaces: 0, fields: 0, methods: 5, attributes: 3
public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2551,6 +2568,31 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+ x: ldc #x // String nativeBytePlus
+ x: ldc #x // String (BB)B
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+ x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+ x: iload_0
+ x: iload_1
+ x: iadd
+ x: i2b
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 15 5 0 arg1 B
+ 15 5 1 arg2 B
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative_host.java"
RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index 37de857b9780..c605f767f527 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -1554,7 +1554,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 9, attributes: 3
+ interfaces: 0, fields: 1, methods: 10, attributes: 3
int value;
descriptor: I
flags: (0x0000)
@@ -1686,6 +1686,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static native byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative.java"
RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index a57907d9398b..088bc80e11c5 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -2743,7 +2743,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 11, attributes: 3
+ interfaces: 0, fields: 1, methods: 12, attributes: 3
int value;
descriptor: I
flags: (0x0000)
@@ -3002,6 +3002,28 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+ x: ldc #x // String nativeBytePlus
+ x: ldc #x // String (BB)B
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: iload_0
+ x: iload_1
+ x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B
+ x: ireturn
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative.java"
RuntimeVisibleAnnotations:
@@ -3024,7 +3046,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 5, attributes: 3
+ interfaces: 0, fields: 0, methods: 6, attributes: 3
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -3148,6 +3170,36 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+ x: ldc #x // String nativeBytePlus
+ x: ldc #x // String (BB)B
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+ x: ldc #x // String nativeBytePlus
+ x: ldc #x // String (BB)B
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+ x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+ x: iload_0
+ x: iload_1
+ x: iadd
+ x: i2b
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 26 5 0 arg1 B
+ 26 5 1 arg2 B
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative_host.java"
RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
index 5a5e22db59e5..09ee183a2dcc 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
@@ -52,4 +52,6 @@ public class TinyFrameworkNative {
public static void nativeStillNotSupported_should_be_like_this() {
throw new RuntimeException();
}
+
+ public static native byte nativeBytePlus(byte arg1, byte arg2);
}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
index 749ebaa378e3..b23c21602967 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
@@ -34,4 +34,8 @@ public class TinyFrameworkNative_host {
public static int nativeNonStaticAddToValue(TinyFrameworkNative source, int arg) {
return source.value + arg;
}
+
+ public static byte nativeBytePlus(byte arg1, byte arg2) {
+ return (byte) (arg1 + arg2);
+ }
}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index ba17c75132f2..762180dcf74b 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -154,13 +154,22 @@ public class TinyFrameworkClassTest {
}
@Test
+ public void testNativeSubstitutionLong() {
+ assertThat(TinyFrameworkNative.nativeLongPlus(1L, 2L)).isEqualTo(3L);
+ }
+
+ @Test
+ public void testNativeSubstitutionByte() {
+ assertThat(TinyFrameworkNative.nativeBytePlus((byte) 3, (byte) 4)).isEqualTo(7);
+ }
+
+ @Test
public void testNativeSubstitutionClass_nonStatic() {
TinyFrameworkNative instance = new TinyFrameworkNative();
instance.setValue(5);
assertThat(instance.nativeNonStaticAddToValue(3)).isEqualTo(8);
}
-
@Test
public void testSubstituteNativeWithThrow() throws Exception {
// We can't use TinyFrameworkNative.nativeStillNotSupported() directly in this class,
diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig
index 3c734bc7e1e3..6c4e4c3eb9be 100644
--- a/wifi/wifi.aconfig
+++ b/wifi/wifi.aconfig
@@ -1,5 +1,4 @@
package: "android.net.wifi.flags"
-container: "system"
flag {
name: "get_device_cross_akm_roaming_support"